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,=.
& 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.
What's the advantage of a tool like this over that?
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 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.
> 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
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.
I wouldn't use gitbutler for what I use worktrees, and I wouldn't use worktrees for what I think gitbutler is aiming at.
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?
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.
* 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.
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 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.
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.
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`.
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.
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`
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.
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
Each developer can modify their .env however they want without having to make any changes visible to git.
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?
Try 'git worktree'
Short answer is that worktrees dont exist in the same working directory like virtual branches do.
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!
But re:jj, Im pro anyone trying to do interesting things around VCS/Git. Its been a very long time of general complacency.
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.
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.
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."
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.
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
[0] https://docs.gitlab.com/ee/ci/pipelines/merged_results_pipel...
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?
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.
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.This is a non-starter, sorry.
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.
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.
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.
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.
UPDATE: add link to documentation https://www.jetbrains.com/help/idea/work-on-several-features...
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.
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 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.
> 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.
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.
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.
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 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?
The rest of it is pure gimmick and would be force turned off in my team.
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".
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.
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.
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.
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.
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.