Hacker News new | past | comments | ask | show | jobs | submit login
A Git client for simultaneous branches on top of your existing workflow (gitbutler.com)
211 points by sandgiant 9 months ago | hide | past | favorite | 115 comments



'Virtual branches' is a nice idea, but I think the 'don't use other tooling' caveat probably makes it a no-go for me.

It's not exactly the same, but since it's how I achieve the same goal currently, I wish it were implemented instead as automatic management of fork-points & rebasing the currently 'active' branch; even if there isn't one you consider 'active' so you want this merge, there still effectively is as soon as you do anything, so it could just choose arbitrarily or create some (possibly temporary) other branch name to show.

Because while I'm a proponent of rebasing, apparently the 'go-to' guy for git on the team, etc. I won't pretend it's trivial to maintain a chain of dependent branches, or not annoying. A couple more aliases than I currently have would probably help, but it'd never be as good as something like OP just clicking to 'rebase all on master', or where x-->Y-->Z(HEAD) 'switch to work on Y but keep Z', etc,=.


Im not following you. What do you mean by “don’t use other tooling”?


> Quick warning. You cannot use both GitButler virtual branches and normal Git branching commands at the same time, you will have to "commit" to one approach or the other.

& see the description of 'the integration commit' for some detail of how they make it work:

https://docs.gitbutler.com/features/virtual-branches/integra...

Basically anything you do with `git` (or another GUI) will get blown away by GitButler; or if you change branch away from it then it will stop working.


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.


You can switch back and forth rather easily. But if you have multiple branches applied, some git commands wont make any sense.


Sorry I didn't realise you worked on GitButler when you asked.

> But if you have multiple branches applied, some git commands wont make any sense.

Sure, because of the way it's implemented. But that's just a variation on what I said really - I like the idea, see value in automating some management so that I can see & work on something that's the amalgamation of multiple branches, but I don't want to (have to) go 'all in' on some third-party tool, it needs to work like an extension of vanilla git for me, so everything else can keep working as normal.

It needs to be implemented in 2D so that git CLI can still understand, to put it in the docs' terms.


Just to clarify, yes, in order to work on multiple simultaneous branches, you need a tool that knows what that means, which git does not really. But you can also very easily run 'git checkout main' and do whatever and then go back to GitButler and restore state. It's pretty good at knowing what mode you're in.

Basically all your virtual branches also live in refs/gitbutler/[name] and GB won't touch stuff in refs/heads/ because we like the idea of making sure we're not clobbering things you don't expect us to. You can just think of GitButler as a tool for managing refs/gitbutler branches.

All git tooling work totally fine, even generally if you're using something (like commit) that doesn't really make sense in the context of multiple branches.


GitButler will literally not work when you use raw git, or other tools.


But even if you do run “checkout” or “commit”, we still notice that you did that and try to put you in the state you want.


It works fine. The only thing that is a problem is “branch” and “commit”, things that use the index. But that makes sense. If you have setup two virtual branches and then from the cli run “git commit”, which branch do we commit to?


This makes sense, but I have to say I would find it much less concerning if doing "commit" would apply to some well-defined selection. Either something marked in the UI as "default" or "first" or something so that normal git operations wouldn't cause failure. I realize it makes one of the virtuals special, which is unfortunate, but it means normal command line workflows would be _safe_ even if they wouldn't always provide all the options.


Two things. One is there is a commit dialog per virtual branch. You do a commit on a branch. The selection is all the files/hunks you see on that branch, or you can selectively commit parts of those changes (such as a add -i type commit). It's pretty much identical to how Git (or any other Git GUI) handles commit selection, except you can have more than one branch that you're working with, applied, at a time.

Also, GitButler, as far as I can think of, never causes normal git operations to fail. It doesn't put the project git repo in a state where git can't operate.


That's great news, and I'll play with it some more to see if I misunderstood the docs. If so there may well be no issue here. Thank you for clarifying.


If it were implemented as a 'rebased chain' as I described rather than a multi-head merge: the active one, just as vanilla `git commit` would.


> The only thing that is a problem is “branch” and “commit”, things that use the index.

A git branching tool that breaks git branch and commit - what a novel concept!


I think many developers don't want to have to jump to another tool. Myself, I've found the VS Code git client "good enough" (even though I have a Git Tower license). Are there plans for extensions for the tools that support them?


Maybe, but I think most developers are fine jumping to another tool if it makes what they do every day easier or faster.

Our goal isn't to replicate all the commands git can do. It's to try to create an interface for writing Git commits/trees that fits a more typical workflow. The UI is pretty different, but the end result is the same. If we can do that _faster_ than you can do on the command line or with another GUI, most developers are pretty pragmatic and will consider it. That is to say, if we do it well.


After watching your (very enjoyable) talk in the other thread, schacon, one thing struck me - there _is_ a way to work on multiple branches at the same time: worktrees.

What's the advantage of a tool like this over that?


It's a good question, I was just working on a blog post - both because worktrees are very cool and also because I think we have a nice alternative to a similar issue.

With worktrees you can have different branches in different working directories and work on them at the same time. But practically, there is very little difference to just cloning the repo twice and working on different branches in the two checkouts.

The way we're doing it, the branches are both in the same working directory at the same time.

This can have a couple of advantages - one is if you have an annoying bugfix and you need it in order to keep working on your feature, you can have them both applied but you don't have to commit one into the history of the other. You cannot do this with worktrees.

Another is trying out multiple branches together. If they don't conflict, you can essentially get the result of a multi-way merge without creating any merge artifacts. Say you merge three work in progress branches to test them together, then keep working on each independently. Also, in this case, you _know_ that you can merge them in any order and the result will work because you've actually worked on the merged tree.


> With worktrees you can have different branches in different working directories and work on them at the same time. But practically, there is very little difference to just cloning the repo twice and working on different branches in the two checkouts.

With clones you have to fetch everything N times. You have to gc everything N times. You have to do `git config --local` N times if the config matters/affects all clones. If you need a local branch in two clones you need to synch. between them. The repository needs to be managed, which means that every clone needs to be managed. With a worktree there is nothing extra to manage.

And when you’re done you remove it via the subcommand and it’s gone.


Reminded me of a product called Rational ClearCase:

> It uses the MultiVersion File System (MVFS) which is a virtual file system that displays specific versions of data stored. In particular, it supports dynamic views which can show an arbitrary combination of local and remote files

https://en.m.wikipedia.org/wiki/Rational_ClearCase


Yeah, this sounds very much like a 'view' in clearcase.

It's a good feature especially with big monorepos where it is helpful to pick and choose a known good version of specific module or subset of files without polluting the history.

Tracking good versions of every single file with huge number of files changing was a pain though.


I haven't used ClearCase, but from the description, I don't think it's much like a view. It's closer to IntelliJ's changelists, but without many of the limitations.

It's effectively a way to take changes you're making and pretend like you did them in worktrees without having to switch back and forth.

Or maybe, to work on several branches, achieve the merged state you want, then split the work up into reviewable branches that can be merged in any order to achieve that final state.


Typical use case for worktree(s) is to work on feature/hotfix branch without touching the current master/wip branch.

So, in the context of worktrees I read _"The way we're doing it, the branches are both in the same working directory at the same time"_ as having both the branch's files (not changes) available in the same worktree.

Thanks for clarifying that it is similar to changelists.


Ha! That’s exactly what I do personally, and I had the same reaction. Worktrees are awesome.


Don't worktrees require more work to actually run your code because of multiple working directories?

If you're using an IDE, you'll probably need to create a project for each working directory. Or maybe you can change the path within the IDE's project.

If you're running a local web server, it will need to be configured for each working directory, again either multiple instances or one where you keep updating the path.

For compiled languages, the builds will take longer. When you create a new worktree, you may have to do a full build. Even if you have incremental builds, as you pull in upstream changes, you'll have to do N incremental builds instead of 1 incremental build.

It's not the end of the world, but it's a bit of hassle and extra computation that isn't needed with just one working copy.


Maybe I don't understand how gitbutler works, but the main reason I use worktrees is actually to keep my IDE and incremental builds getting confused when I switch branches, on branches that have lots of differences. It works really well, and I can fix stuff on old versions of our app in a jiffy.

I wouldn't use gitbutler for what I use worktrees, and I wouldn't use worktrees for what I think gitbutler is aiming at.


Since we write out our virtual branch artifacts into refs/gitbutler/X, you could actually pretty easily setup worktrees from each of them to do this type of work on. Perhaps setup a post-commit hook to update any active worktrees automatically too. Might be an interesting way to effectively work on several worktrees from a single working directory.


That's interesting. I'll try this out when windows support ships and keep an eye on it in the meantime.


That would be brilliant and an instant subscribe from a long time tower user


Not sure this is something I need. One thing I do need though, maybe someone knows a solution:

Often I find myself maintaining a handful of "local" changes. I make some changes that only make sense in my local environment, that I don't want to push.

What I end up doing is maintaining these changes as a commit, committing on top of them, and using `git rebase -i` to periodically move them up. Then before I push, I have to temporarily rewind the branch to remove them, push, then cherry-pick them again.

It's all a bit awkward and I would love a tool that maintains a kind of "virtual branch" that isn't shown but is automatically re-based on top every time I make a commit, maybe letting me resolve conflicts or even telling me ahead of time if I've created one, before committing.

Someone must have already solved this, or am I doing it all wrong?


It sounds like a project structure issue where local environment config that shouldn't be tracked is (or a lack of optional additional config that could be specified locally). Or maybe I'm misunderstanding the sort of change you're talking about.


This is what I thought, too.

I generally try to have support for this in the application, using a `.local` config file override which is in .gitignore. If one person has this trouble, chances are the rest of the team does too, and making this change is usually less time than the team collectively spends messing with it in a week.

Sometimes there's an existing workflow everyone just accepts (project in a specific path; some dependency manually installed locally; specific HOSTS alias to the database server IP; etc) but so long as you don't break that while also making it better/easier, I've found it's usually accepted.


Specifically, the fix is usually:

* Rename the configuration file in the repository to add an `.example` suffix (preferably, to an empty `-local` file if includes exist)

* As part of the build system, copy all `.example` files to remove the suffix, but only if they don't already exist.


Stash?

That's what I do, in any case. If I have a few various small configurations I want to carry around, I stash them when I need to swap branches and then pop them off.

That way it's never in the history and there's none of that nonsense to deal with.


I do this all the time in almost any repo I work in. I usually just keep the difference as “junk” in my local work tree.

I never git commit -a anyway, my commits are always kinda prepared and meticulous.

I just live with the spurious diff and extra stashing. All alternatives seem to complicated. I do love the way Jetbrains IDEs manage changesets though. It is a big improvement over only (not) using git for this, but I rarely use their IDEs nowadays.


> Then before I push, I have to temporarily rewind the branch to remove them, push, then cherry-pick them again.

You do not necessarily need to modify and restore your branch head just to push in this case. If you have e.g. two temporary commits at the top of the branch, you can use e.g. "git push origin HEAD~2:master" to skip those commits when pushing.


ok that one's a cool tip


I think this tool would help that, because you'd stick your only-makes-sense-for-you changes on `my-eyes-only` branch; that branch would always be in your 'integration' commit, along with whatever other 1+ branch(es) you were working, and you'd just only push the latter.

That said even without GitButler you can improve it a bit: you can `git push <remote> HEAD^:<branch>` rather than 'temporarily rewinding the branch to remove them'. You could also consider just never committing it, stashing the changes if you really needed them out of the worktree, and if they're whole new files adding to `.gitignore`.


Oh and enable rebase.autostash config option to help with not committing them, so you can still rebase without constantly having to deal with them manually.


This is true. For the use case you're talking about, just sticking some changes in a semi-ignored virtual branch basically will do what you are talking about. :)


What you are describing sounds very much like a common situation when working downstream of an open source project. I believe (and agree) the commonly accepted solution are patch queues, for which a number of tools exist: Andrew Morton's patch scripts, quilt, mercurial queues, guilt, stgit, to name just a few.

As you describe, just maintaining a branch that regularly gets rebased onto the latest upstream using only git's inbuilt tools also works. I do that, it's a good enough solution when keeping the history of old branches by merging them into a "patch queue history" branch before rebasing so they don't get lost.

Yet, I feel a much better gui tool to support this workflow is both possible and desirable. I totally agree with the venerable schacon in that regard. So whenever I learn of a new git gui tool I get excited and hopeful.

I actually started a tool some 15 years ago, with the vague hope I could one day open source it, or at least show it around so that others can take the good ideas and run with them to implement in their tools. Unfortunately I never had enough time to move it forward beside my day job.

15 years ago I built atop mercurial queues with a Mac gui. Of course a proper tool should be built on top of a cross-platform gui and libgit2. I can't do much at the moment, but I still hope one day someone will build on my ideas or come up with better ones.


Not a complete solution to the more generic problem you described, but enough for me most of the time:

1. I try to arrange things in a way that I can keep my local changes in files that are not in the repo. For example: If your software loads a file from several places and the more specific one (current dir) wins over a more generic one ($HOME or /etc) you can try to keep a local one.

All these files end up being .gitignored from a place that is not in the repo itself and not shared: Either in the global .gitignore or .git/info/exclude. If the files don't have to be in a specific place I put them in a subdir called aux, which also contains a .gitignore with just an asterisk (*) on the first line. That way it never gets added and it doesn't leave a trace in any .gitignore outside of aux.

2. Files that are checked in but need local modifications are marked with `git update-index --assume-unchanged`


If you need a lot of local changes that you can't commit, then either your repository or your project (or both) are organized improperly. You must have committed files to git that should never be committed or you project doesn't allow you to adapt to the local environment without changing files committed to git.

For example, you may have hardcoded paths to tools and compilers, but in your local environment those tools have different paths. This is a problem with the project organization.

Another example, you may have something like VSCode settings file which somebody decided to commit to git, but those settings only make sense in his environment and not anybody else's environment.

Instead of searching for a workaround to these problems like virtual branches, you should push for fixing your project organization and you git repository.


I have the same problem, but I haven't found a convenient solution yet.

Though stated differently, it's exactly the same thing: https://stackoverflow.com/questions/76717374/how-do-i-keep-a...

A somewhat related but inexact match is when you want to ignore changes to some files (e.g. I sometimes vary some test code locally and don't really want that to be exposed to others) for which I found a convenient fix on SO and compiled into an answer https://stackoverflow.com/a/70075113/3858681


It depends on the kinds of changes you mean. The kinds of local changes I have are just env variables that allow the software to run locally a little bit differently than in the production environment. I'm able to do this by using a .env file and a library for my languages of choice that read a .env file if it's there, but use defaults that make sense for prod when it's not there. Then the .env file is gitignored so it doesn't make its way over to the production environment.

Each developer can modify their .env however they want without having to make any changes visible to git.


I also have this issue. What I do is work in a separate branch and cherry pick commits over to main then rebase.

I bet that what you describe could be implemented with stashes and scripts run on hooks, but that feels like fighting the tool too much. There's probably something we're missing?


I think those local changes should be treated as config that can be overriden in the local environment through a combination of files that are listed on .gitignore and environment variables.


Stacked Git (stg) will let you push and pop commits. Pop when publishing, push back when you need them again.


There's a very similar feature which allows you to checkout multiple branches from a single repo, and comes out of the box with git.

Try 'git worktree'

https://git-scm.com/docs/git-worktree


One of the first comments was this, and there is a longer answer to why this is different if you’re interested.

Short answer is that worktrees dont exist in the same working directory like virtual branches do.


Very cool, congrats Scott!

I have been getting very into jj over the last few weeks, which is larger in scope, but also currently uses git as a backend of sorts and supports similar workflows. Do you have any thoughts/opinions/comparisons to it?

EDIT: I just mentioned this in the jj Discord and schacon is already in there, I didn't realize!


I think jj is super cool. But I like the idea of a killer GUI that makes so many things so fast and easy to do that I would use it instead of the cli. Ive never used a git gui for more than a day. Ive used GB daily for months and I love my dogfood.

But re:jj, Im pro anyone trying to do interesting things around VCS/Git. Its been a very long time of general complacency.


Cool, thanks! I am also in the "never used a git gui for more than a day" camp, so that makes a ton of sense! Looking forward to giving this a shot.


I'm very unclear why this "takes over" the git repository to work. Like even if that was absolutely necessary - and who am I to say it's not - ...surely the "actual" repository can simply live somewhere else and be manipulated?

How do virtual branches differ so completely that managing them isn't just a bunch of behind the scenes checkout/commit commands being issued to another repository somewhere, even if the local worktree needs to be separate?

Particularly because the idea of their being a base branch for virtual branches is pretty much the git branching model. Git and it's tools understand this perfectly fine provided you're using branches, and moving things between branches is supported via cherry-pick.


There's a lot here that is a little confusing, because I don't think almost any of what you're saying is what we're doing.

It does not take over your repository. We try pretty hard to not touch as much as possible. We do have a second place where we store metadata on everything we're doing, but it's not in your git repo. If you delete us, your repo is only very minimally added to.

We can't do virtual branches by wrapping git concepts and data structures, because there is just no concept of more than one applied branch. HEAD can only point to one thing, we're trying to make the concept of HEAD be able to be more than one thing and land changes on any of them depending on where you want each hunk to go. Git just cannot do this. It has one HEAD and one index and we need multiple of each for what we're trying to accomplish. We try to be tool-compatible and touch as little as possible. We update the index to match what is likely expected - ie, make a normal `git status` match the list of files you see in your GitButler workspace as changed, etc. We write out `refs/gitbutler/[branch]` for each virtual branch you manage, so you have real git refs to work with. But there are lots of things we want to do that Git simply does not have data structures for.

Finally, Git does not have a concept of a base or default branch. There is no special branch in Git. You have HEAD, but that changes. You have tracking branches, but that's per branch. Nothing in Git proper says 'origin/master' is special in some way except convention. The tool itself has no idea what is the "main" branch. GitHub does, with it's "default" branch, but Git does not.


For whatever reason this makes me think of having multiple worktrees in the same directory tree :) like .git (normal), git-1, git-2… so you have X HEADs. But that is probably way off anyway because of https://news.ycombinator.com/item?id=39374140


As usual couldn't recommend stacked git [1] enough. If you prefer CLI it greatly reduces cognitive load about branches/rebases especially if your work with review server allowing to see changes between force pushes (gitlab, gerrit).

[1]: https://stacked-git.github.io/


The idea with this seems to be - I'm working on a branch and can't easily switch right now to put out a patch, or MY current branch HAS the patch and I want to push that out before the entire branch is ready so I make the stg commits and pushes and that patch is released.

In their example [1] at the very end they commit --all of their patches into the main patch and I guess merge that into main.

I'm kind of confused on the commit --all part. If you're putting out single patches like stg committing your current file, what benefit do you get by making multiple patches, not pushing them to main immediately, but holding them as a patch series until you're done to then merge all together? Is it mostly for the developer to keep track of X change = Y patch while you're fixing multiple things?

Like, I'm in a branch right now that unfortunately I've got 3 patches doing different unrelated things. But the changes are in a lot of the same files, so to use this patching I would have to pull out the other 2 patches in the same file, stg commit the one patch, then add patch 2, stg commit patch 2, etc that eventually just commit --all into the same file but are now identifiable by X change = Y patch?

Definitely a lot easier if had I had started out using this and stg committed patch 1 before writing patch 2 and 3.

Am I right about all of this or missing anything, or completely off? I wrote it in another comment but I deal with a lot of massive infrastructure changes where one push is deploying basically entire datacenters of load balancers and clusters, etc. a LOT of files are modified at once, so I'm in repos for a WHILE until I do my push and it makes it very hard for me to switch branches if I need to hop around real quick, I'm super apprehensive about stashing and changing branches in the middle of big PRs but I really couldn't explain why. I always worry I'm going to lose something or forget about the branch, etc.

I could definitely patch on things like, "This patch adds the loadbalancers and routing, this patch adds the cluster, this patch adds the security."

[1] https://stacked-git.github.io/guides/usage-example/


You are right about conflicting changes. They are hard to manage as order independent changes with any tool. In stgit context I often split big change into multiple ordered patches and yes I know what piece (incremental edit) should be added to what patch. Classic example: doing refactoring around main task. Refactoring goes into one patch, main changes into another. It allows greatly simplify review process.

But common case for me is to have multiple independent patches for different features/bug fixes. For example one current project has 12 patches, another one has 5 patches. Two or three are dedicated to current work. Rest are drafts with ideas.

> I'm kind of confused on the commit --all part.

Me too. Almost always task is represented by a single patch. And I push changes on patch basis. One patch -> one review.


Edit: I now realize much (most?) of this already exist as part of the temporarily hidden Timeline feature in GitButler: https://docs.gitbutler.com/features/timeline

Now we just need automatic (possibly virtual[1]]) commits that get created whenever one does a refactoring or other wide, sweeping tool assisted changes and soon my skills as dev archeologist would either become less valuable (because everyone can see what happened[2]) or more valuable (because just like no one I know except me uses bisect no one will use this and it will simplify my job.)

This is (in my opinion) a great idea for a paid IntelliJ/VS Code/etc plug in but I realize I won't have time to do it.

[1]: "virtual commits" is just my marketing speak for adding extra metadata without polluting the branches we usually relate to. One possible way are branches named virtual-<uuid> and IDEs that support them will hide them by default. The plug in commits automatically on the hidden branch while keeping the normal user facing branch at its current commit, and everytime one commits the normal user facing branch the metadata branch is merged as a formality.

[2]: Maybe when I hover over a function I see its refactor history? Maybe I can click on a commit to expand it and see 4 virtual commits:

- edit + successful test,

- refactor + successful test,

- edit + test fail,

- edit + successful test


I think GitLab does something like this behind the scenes when you tick the Merged results[0] box. Fake commit to run CI against.

[0] https://docs.gitlab.com/ee/ci/pipelines/merged_results_pipel...


Thanks!


Don't know if that's what you're looking for but JetBrains tools have a Local History that saves all your local edits. It also notes whether unit tests where passing/failing at that point and let's you revert separate hunks.


I know. It is really useful!

The difference here is it would save it to the central repository so I know not only the steps I took but also the steps my colleagues took + K don't think local history track reason for change?


Yeah this is too much marketing speak to be able to relate to concretely.


I started using this yesterday.

I'm a little unsure how to pull multiple real branches into my workspace.

It'd be nice if I could have a virtual branch overlay a real branch so that I could continue working with both the advantage of GitButler and maintain productivity with colleagues that don't use it.

As it stands right now, I'm a bit unclear on how to operate with others who use the old fashioned branching strategy

I really love to tool though. I watched a talk from Scott yesterday, and he has so many good ideas of where to take Git next.

I also LOVE the "CRDT all the changes" idea, and have tried to implement similar solutions using inotify and a "stack of patches" and BTRFs CoW for space saving, with limited success.

https://news.ycombinator.com/item?id=39356042


The CRDT thing happens in GitButler, but we hid the UI to search and find old changes for now (you can see what it looked like here: https://docs.gitbutler.com/features/timeline). We still record the data locally and could theoretically recreate the state of your working directory and branches at any time. If you're curious, join our Discord and I can show you how we're storing it, you could easily use it in interesting ways until we resurrect a UI for it.


You should be able to do this pretty easily. We list your other local and remote branches in the sidebar. If you apply them, they are essentially converted into virtual branches (remote targets too, I believe)


I think I got it working today. Not sure what I was doing wrong before.

Also, you I recommend that you add the capability to pull different hunks out of the commit, and separate/unsquash them, or even move them to different branches. The UI is begging me to drag parts of the commit out. I can squash commits, but it doesn't seem like I can do the opposite.

Use Case: Just now I made a change to a config file, and a build tool also updated the version in this same config file. I want to split the change out.

Using the CLI to split the commit, I would do this:

  git add -p <file>.              # partial stage
  s                               # for each hunk
For splitting the hunk into a branch, I would do this:

  git checkout -b new-branch-name
  git reset HEAD^                 # if I already staged it
  git add -p
  s                               # for each hunk
Using Magit, splitting out the commit:

  M-x magit-status
  TAB                             # on the file diff
  s                               # per hunk
  e                               # when I need to refine it manually
Using Magit, to move hunks to a different branch:

  M-x magit-status
  TAB                             # on the file diff
  b c                             # new branch
  <same process as before>
  b b                             # as needed, switch branches
Again, want to thank you for you work here. It's really nice, I already am getting my team onboarded to it.


So, yes and no. You should be able to do `reset HEAD^` equivalent by just hitting 'undo', which is essentially what that does. You can then re-commit stuff or drag the newly uncommitted hunks to other branches.

But it's only for the last commit, so something in the middle you can only squash, you can't unfold into two commits or something. Totally doable, but not in our UI yet. (we will do that someday)


> By default, everything in the GitButler client is free to use, but we credit ourselves as the committer in your virtual branch commits. Community members and supporters of GitButler can turn this off.

This is a non-starter, sorry.


Totally valid, but FWIW this can be turned off without becoming a supporter: take a look at the source for the settings page for a hint.


It’s hard to get sold on a Git workflow tool that proposes a new (or not supported by git(1)) way of working outside of a long-form article. I see a lot of little paragraphs here and there on the website, like “faq” answers about specific things. But I can’t just piece together “virtual branches” and “doing things at the same time” (or something) and get what this is about. The GitHub blog does as good job of presenting new git(1) features: (1) explains the problem or context, often using several paragraphs and then (2) presents how git(1) can now solve that, concretely.

I would need a concrete case. Maybe how I am working on five different “features” that all depend on each other, maybe serially. And how I can work with a pull request (forge) somewhere and get those merged and at the same time get those virtual branches updated and managed for me. Or at least that’s what I imagine that this is about. Also how existing solutions (there are many of them) fall short or have different tradeoffs.

PS: Including “git butler” in the title... would have been useful.


This seems like an incredibly poor idea to me. You now have the problems of rebase (your commits no longer represent a consistent repo snapshot), but even worse (your commits _never_ represented a consistent repo snapshot!).

Is there a way to identify commits made by GitButler? Can I configure my host to reject them automatically?

And the "generate a commit message for me" button really nails the kind of poor decisions that lead here.


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.


GitButler creates normal Git commits and trees with libgit2. It's not dissimilar to using a tool that implements it's own interactive adding or something. The point is that Git is the database and Git hosts (such as GitHub) matter in our workflows, but actually creating the trees that represent the trees you want to share can happen in any way. We are changing how to conceptualize and execute how to get the trees you want, but the output is Git objects.


Sure, you can do stupid crap with the git CLI.

But scaling up the magnitude of that crap, turning into it into a headline feature, and making it easier to forget that you're doing it in the first place doesn't address why it's a bad idea or help the situation at all.

Or to relate it to another feature: yes, git supports rebasing. That doesn't mean that it should be a regular part of your workflow, or that it would be a good idea to build a new UI on top that focuses on it.


You seem to have a poor understanding of what git does and how to use it to work effectively. It's a versatile tool that accommodates various workflows, even ones that are radically different from yours.

It's fine, everyone started from scratch at some point and it takes time to get there, but meanwhile you probably should be more open to a possibility that when someone does things differently than you, it may not necessarily be "stupid crap".


Commits always represent a consistent repo snapshot (it's what they actually are technically), and they don't have to represent a snapshot of your worktree at all even when using plain standard client with `git add`, so not sure what's your point there.


I'm using git-branchless today, with mixed results (have a large-ish monorepo, it's slow when every commit is tagged by CI and restacking broke and only works on a single branch) - I could really use an ascinema recording of a typical two-or-three-branches-in-parallel workflow which need daily rebasing.


Nice, I’ve never used git-branchless. GitButler is only a GUI for now, but a CLI/TUI would be very cool and we would love to get there.


Intellij IDE support this through changelists.

UPDATE: add link to documentation https://www.jetbrains.com/help/idea/work-on-several-features...


The listed cons of “changelists”:

1. Not part of Git at all—not committed

2. Can’t be shared because, duh, not part of Git

That sounds like a fail to me. The whole point of using Git commits is that once committed, it’s just there. And things can be shared. The point of collaborative version control.

I can manage multiple changes at the same time—that’s the index, e.g. selectively apply changes to commit in Magit. I can commit and shuffle things around—that’s rebase, cherry-pick. I can push and pop commits with some third-party stack management tool that integrates with Git.

In other words: I can use Git or I can use Git + whatever rinky-dink non-database patch management (?) tool that Intellij (IDEA) has implemented. One tool is simpler than two.

This feels like we’re back in 2002 and having to use a patch management system on top of the rigid centralized VCS in order to get some local freedom. But we already have that…

But Intellij’s thing is probably more nimble for managing a lot of unrelated things at the same time. Not that I want to do that though because of increased chances of merge conflict and just the sheer brain-overhead of it all.

If you need to change to an older commit and avoid the thrashy, unreliable indexing of Intellij (IDEA): use git worktrees.


Sort of, but it's honestly closer to normal git branches. If I'm not mistaken, you can't have multiple changelists applied simultaneously. You still have to switch between active groups of changes, no?


Changelists are more like splitting your local changes into several groups. The idea is that you usually want to commit only one group of changes while leaving others uncommitted. For every changed line the editor will show you whether it’s modified in scope of your active changelist or in scope of a non-active one. Also, you can easily ‘shelf’ and ‘unshelf’ a changelist.

I was actively using changelists for some time, but later switched to git worktrees instead. Now I usually have only 2 changelists: ‘Default’ and ‘Hacks - don’t commit!’.


you can have any number of changelists (or changesets) active at once.

You can actually have changes in the same file with some lines in different changesets.

When needed, you can commit changes to git (when you finish doing work) to make them visible to coworkers.


I see. Well then there are some similarities. Though you can use ours with any editor :)


yours is GREAT!


This looks awesome. I've used git for over a decade and I always have to look up rebase, reset head, etc commands that I use maybe a few times a month.

> Undo, squash and amend your work by just dragging and dropping. No need to wrestle with rebase -i.

I think one thing that would be really cool to add to this and other git actions is to put the git commands that will be run based on the users inputs somewhere on the screen so the user doesn't COMPLETELY forget how to use git, and actually may get better at it with the additional help. Some may think that would lead people to not using gitbutler anymore, but no way, I am absolutely happy never having to write another git command beyond commit and push. Every time I have to do something I don't usually do in git it slows me down by 5+ minutes and it's super annoying and god forbid its a too-big PR that I have to cherry pick etc.

Also, another thing I am DYING for, none of my IDEs (intellij/vscode) as far as I know have extensions/options for it - if I do a commit and push in a vscode session, COLOR THE FILES SO I STILL KNOW WHAT I EDITED. DON'T UNMARK THEM AS EDITED. Make them yellow or blue or something, I don't care, just color them. You can absolutely track git diff / cache etc against 1 or 2 previous merges and base file colors on it. I do this all over my CI/CD scripts to determine whether or not we need to do a docker build/deploy etc or not based on files that diff from origin/main + your current branch 1 behind. If you didn't modify a Dockerfile, we don't do a docker buildx with your push.

I HATE when I have to commit/stash a massive PR with 30+ files edited because I need someone else to pull it or whatever and then have NO easy way to see what I changed in the commit vs main in my UI because as soon as I commit and push all of the tracked files change color to the "im just a file" grey color.

It causes me to commit a lot less when I should commit more. I'm pretty sure at some point I'll have to dive into js and write this myself. Probably just store modified files in a list and diff against main. I have an extension (wakatime) that tracks how long I've written code (not idle time at all, actual typing) in a BRANCH so you can definitely track a session based on something like your time writing code in a branch and cross reference that vs modified files.

The current branch I'm in I've written 4 hours of code in over 1-2 weeks, so I just need to know in that 4 hours what files I touched.


An aside but this is giving me ClearCase config_spec vibes.

Memory is hazy - possibly due to trauma - but you could create a config_spec which would make your view (roughly: working tree) pick up different bits of the filesystem from different branches. Branches might only exist on some of the filesystem, depending on how you set up auto branching and what you checked out, and you had to know what the config_spec rules everyone was working to were or the whole repository was effectively broken.

It was very complicated to get your head around - I guess not helped by the codebase I was working on at the time which tended to concentrate change in the same 20 files.


I feel like this is much different. We're not saying "patch these things together into a materialized view", we're saying "here is the diff between your working directory and origin/master, which branch owns which hunk" and keeping that mapping around.

We keep monitoring it until we see changes upstream (someone merged into master) and then we let you update the difference (merging or rebasing commits you made if needed)

We're not inventing massively new mindspaces.


Spec was a huge pain. For our project it contained around 50 directories to fetch from. Directory per component with own versioning. Only a few persons know working combinations you could use.


I have to say- I'm just deadly scared to do this

I'm already quite confused when something wrong happens and I get confusing git messages. To add some more abstractions on top of that... just so scary.


I found the virtual branches compelling, but when I tried it out, I got myself into a mess.

I think that this could all be done using real branches, plus an integration branch with post-commit hooks to backport each commit onto the "default" branch and recreate the octopus merge "integration" commit each time. Would that work, or am I missing something?


Looks like a gimmick to me. You can achieve this easily by working on a scratch branch, and then, once ready (and with git commit -p), building the commits you want. Then, you cherry-pick them into other branches.

The rest of it is pure gimmick and would be force turned off in my team.


It's really confusing to have a windows logo underneath the download link, but then no download for windows.

I guess that windows icon is supposed to be grayed out, but all 3 icons are shades of gray so it's not obvious at all until you mouse over it and see "coming soon".


I think "non-conflicting" is the crux here. I understand this is at the minimal viable product stage, but there are a number of ways to handle that already. To me it only starts to get interesting when conflicts are involved.


The little demo videos embedded on the home page are really sharp! How were they made?


We used an app called Screen Studio.


doesn't work with other tools + GUI only => non starter


Slightly offtopic, but because this was prominently featured in their demo:

AI generated commit messages are horrible. I'm not opposed to them in principle, but currently every implementation I have seen uses the code change itself as the context to generate the message. This is just wrong. Commit messages are there to convey information that's explicitly not contained in the code itself and shouldn't just repeat or summarize the code changes (that's what the diff is for). They should contain the reason why the code change was made in the first place, approaches that were considered and did not make it into the code change, etc. Short everything that's not contained in the diff.

AI commit message generators incentivize people to write the wrong type of commit message.


Well, two things. They're not on by default, you have to enable them. Both because it means we have to send your code diffs outside your machine, but mostly because lots of people don't want them.

The other is that the point is not to take away writing good commit messages. The opposite. One item on our task list is to create the _best_ commit message editor we can think of. One that Linux kernel hackers would want to use to craft a great commit. But that's for a bit later.

The AI thing, in our minds, is to eliminate "fixed some stuff" messages that have zero semantic value. If you're committing just to push, you might as well have AI give you some searchable text in less time than it would take you to write a crappy commit message.


I totally see where you are coming from, I've just seen the fallout from more and more tools adding similar features and the net benefit has not been there, quite the opposite. It makes it easy to do the wrong thing.

That being said, with enough context (slack threads about the feature, zoom meeting transcripts, design docs, etc) I could totally see AI commit message generation being great in the future, but that's obviously a harder problem to solve than just piping the diff to OpenAI or similar.


On the positive side, I really like that someone else out there understands that git is mostly simple to understand, so long as you can see all of the concepts laid out for you, and that no kind of node-line timeline or branching tree structure is ever going to be a replacement for a graphical layout of all of the git features. For some time, I thought I was going insane because every time I said "working with git sucks; I wish someone would make a decent GUI", people would insist that 'actually, you should try [x,y,z]; they're really good", only to find out that it's the same command-line-headed nonsense that every other git client thinks is UI. The only thing that saved me is when I started to explain to people what I mean, they would go "oh...yeah...I guess that would help? If you didn't know git? Whatever. Seems like a lot of work for nothing."

And...yeah. That's always been my impression of it. That if you know git well enough to build an app that makes it simple for new users, you might not understand why a new user would need it 'simplified' in the first place. So far, I've chalked it up to that being a frustrating but unavoidable outcome.

So all of that is to say, I'm glad someone built something that treats branches like a "thing" I can "mess with", instead of a timeline that I can indirectly affect through various incantations. And seems to understand the utility of (if not the necessity for) obfuscating a lot of 'stash->checkout->unstash'-type silliness, and all of the branch acrobatics you have to go through to do something as simple as "uncommit a change", or "move this commit's changes over to this other branch". This seems, to me, like a very good idea.

On the other hand, what I'm not particularly thrilled about is the apparent ownership this app takes? Not having interoperability with standard git is a non-starter for me. I'm not going to deal with anyone's proprietary "virtual" anything; there's not nearly enough utility that can be supplied in a VCS that would make lock-in seem valuable to me.

And of course, everything that flows from this mindset is just as tainted. If I have to use your app to create the branch that can be visually controlled, then I won't use your app. It has to work with repos I'm already using. If I have to learn additional configuration or introduce any non-standard areas into my codebase that will cause interactions with other packaging systems to break, that's a no-go.

Git is good. Git works. Don't try to be "better than" git. At least not right out of the gate. Figure out how to make an over-the-top git UI and you've got yourself a customer. But I can't figure how anyone could make an 'embedded' git UI (that changes the way I use version control) that would be useful to me, in any way.


There are some good points, but there are things I want in my tooling that git just cant do. We need to keep some external data structures. However, we are as compatible as we can be. We keep the index in a proper state (union of all the head commits on all applied branches) and we write out refs for each branch (to refs/gitbutler so as not to clobber stuff in the normal namespace). You can still do whatever - base worktrees off the branches we write, etc.


Yeah, but if I have work in the virtual branches, how is that represented to git? Does it even exist in my local repo, or not, until it has been "handled"?

And 'handled' brings up another point because I don't even know what to call this intermediate state of existence that isn't a domain of git. When I'm done with a virtual branch do I... commit to it? Collapse it into a real branch? It's new terminology, new concepts, new X to deal with something that COULD have been handled with just git. Messier, sure, but messy is fine, under the hood. So long as the UI treats it the same, who cares how nasty the git had to get?

I'm sure there are things that you simply couldn't have done with pure git. But what I'm less sure of is how useful any of those things are. Let me tell you this: auto branch naming? That's not only not something I would use, but something I would consider an anti-pattern. I never want my utilities doing my work for me. I only want my utilities making my work simple and seamless. It's the focus on unimportant and anti-useful stuff like that which makes me concerned about my experience with the product overall.

Again - very happy that you all have put this together! It's a great implementation for the things you are wanting to do, as far as I can tell. It definitely feels, to me, like you've got a winning product on your hands and that my complaints aren't relevant. I'm just not in your demographic. But if there is any question about "who" you are losing, I can at least give you one point of data to say that I'm averse to any productivity app that doesn't trust me more than the app. And I think your app makes some strong assumptions about what it "needs" and that is a detriment to the end user, no matter how "necessary" it feels.


> Does it even exist in my local repo, or not, until it has been "handled"?

Yes, we write the virtual branch states into your git repository under refs/gitbutler/[branch] so you have them represented as real git references.

There is not a lot of new terminology. You commit to your branches, push them to GitHub, open PRs, track upstream work. It's really just that you can have more than one of them applied to the same working directory at the same time.

> auto branch naming?

This is only done if you have the AI tools turned on and it's often just a placeholder. We basically want you to be able to use anonymous branches - not having to name them to start them. Think of it as a temporary description if you want, you can as easily rename them as name them in the first place. Also, I rather like them, they're often more descriptively correct than I would have done in the first place.

> doesn't trust me more than the app

This isn't our goal. Our goal is to let you delegate things you don't want to do to us in some cases and make cool things a little simpler so that more people can utilize them. But you have to _opt in_ to the AI stuff, it's not the default. Otherwise they're just called "Virtual Branch 1", etc until you rename them.


Oh this is very very cool! Way better than dealing with change lists on IntelliJ.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: