gitbutler
gitbutler copied to clipboard
UX of virtual branch commit states (discussion)
Background:
The nature of branches having a local and remote equivalent means that commits can be in multiple states.
- If the local and remote are identical, there’s nothing to do
- If the local has commit(s) that are not on the remote, those commits can be pushed
- If the remote has commit(s) (from another user or the GitHub GUI) that are not local, they can be pulled
- If the local branch has non-punished commits and the remote has non-pulled commits the branches are diverged
I find this graphic by Julia Evans to be quite nice at illustrating this:
Problem
The lanes GitButler renders for virtual branches don’t represent the need to pull and diverged states clearlly. To illustrate this, I made a graphic showcasing the way the 4 states are shown by GitButler
Current state in GitButler
Below are 4 screenshots next to each other, covering the 4 states mentioned previously and how they are represented within GitButler virtual branches, alongside some commentary. I generated these states by making a commits using the GitHub website.
Comments from the graphic 👆 (the numbers in blue)
- (1) “Upstream commits” duplicate what is local
- (2) “Merge” is misleading as this would mean simply pulling the remote commit
- (3) “Remote branch” is a misleading term as here we show only local commits
- (3) “Force push to remote” doesn’t make sense as this is in the context of remote commits
- (3) “Force push” may have unintended consequences (losing the remote commit)
- (4) Duplicated from below
- (5) “Merge” is a bit misleading
- (6) “Force push to remote”, unclear difference from the other force push button, possibly unintended consequences
- (7) Possibly unintended consequences
Lets discuss 💬
The purpose of this GH issue is to invite a discussion and maybe go over some drafts / ideas that come up. @schacon, @mtsgrd, @Qix- @PavelLaptev let me know if I have missed something
@krlvi I should perhaps know the answer by now, but what is the difference between "upstream commits" and commits on "remote branch"?
@krlvi I should perhaps know the answer by now, but what is the difference between "upstream commits" and commits on "remote branch"?
I view the current state as a UI (UX) bug. In terms of what goes in each group:
remote branchappear to be showing commits that are both local and remote (with the teal and purple color) - this is misleadingupstream commitsappear to be showing the branches that on the remote.
(very likely the way we have the distinction now is something for us to fully redo)
Trying to illustrate, let's say you start a new branch and commit twice (A and B), you have two "local" commits:
Then you push the branch to GitHub, now those commits are on the server as well, so we let you know that by labeling them as being on the "remote branch":
Ok, now you commit on your branch again but haven't pushed it, so now your commits are in two different groups. One that is local only and one that is pushed:
Finally, someone else pushes work to your GitHub branch (something not created locally by you). Now you have an "upstream" commit.
Now lets say you merge in the upstream commit:
Now you have a few local commits (the merged one included). Now you push, everything is "remote branch", because all the commits are on the server:
I think this is the minimally important distinction. Is a commit:
- present locally
- present on a server
If it's 1 only, it's in the "local" group
If it's 1 and 2, it's "remote branch" group
If it's only 2, it's "upstream" group
We can rename them or differently visualize them, but there are practical implications to each of these groups.
- "local" grouped commits can be rewritten without needing to force push
- "remote branch" grouped commits are possibly on someone else's computer and more care needs to be taken
- "upstream" commits are not present in your working copy and could be conflicting and will need to be integrated somehow
Thanks for that illustration! I was thinking - we currently linearize the sequence, the way we show it, even when there is a merge. Perhaps that's a big source of confusion.
Since we allow merges (and many people prefer it) perhaps we can try visualising it in a non-linear way. Even as a rebaser-person I'd find it perhaps useful.
It could be interesting to pull the graph component out to the side and illustrate the topology a little bit more, rather than flattening and simplifying the relationships (esp where we have merges). jj does this in an nice way:
It would basically be git log --graph --oneline [vbranch] ish output, but prettier and a little more clear.
The one state that GB allows but is not in your list of 4 states is having pushed a commit (or several) to the remote, and then undoing those commits locally. This results effectively in state 2 but requiring a force push if no changes are made after the commits are undone (eg. you just want to change the commit message). However I think once changes are made it should probably act more like state 4?
@KroniK907 in that case, the old commits become state 3 ("upstream") because they are on the server under that branch but not local. the new local work becomes state 1 ("local") as they are not upstream. You could merge them back in, the way they're done in the diagram, but more likely what you want to do is force push.
You're correct though, when that becomes problematic is when someone else pushes on top, so you want to effectively cherry pick from upstream and then force push.
I think this is the minimally important distinction. Is a commit:
present locally present on a server
I took the example screenshots from the parent post and cropped out just the commits, leaving off the groups they were in. I also removed the duplications and just spelled out in english where the commit is.
It's crazy how much simpler the same example becomes when we skip the groups and just spell out what is where, and no duplicates:
Maybe we can design something that communicates the thing that in english using shapes and colors and making it obvious at a glance what is where?
Graph idea
We could in theory draw a mini graph of the commits like this:
(the order of
commit 3 and commit from remote 2 I presume comes from the timestamp, and of course it would be important to be able to clearly distinguish at a glance what is on my computer and what is not)
It would basically be
git log --graph --oneline [vbranch]ish output, but prettier and a little more clear.
I tried the following while my vbranch is in the 4th state (diverged)
git log --graph --oneline main..$(git show-ref refs/gitbutler/testing)
* dafd306 Commit 3
* c0a106e Commit from remote
* 250db6c Commit 2
* fdd15ea Commit 1
* 828305c (origin/main) Merge pull request #12 from krlvi/Virtual-branch
If i log the origin one git log --graph --oneline main..origin/testing:
* 884e7d0 (origin/testing) Commit from remote 2
* c0a106e Commit from remote
* 250db6c Commit 2
* fdd15ea Commit 1
* 828305c (origin/main) Merge pull request #12 from krlvi/Virtual-branch
I guess it is not showing them as separate lanes because there they are not in the same place (one is in remotes). @schacon is there a flag to have them show as 2 lanes next to each other?
@krlvi
If i log the origin one
git log --graph --oneline main..origin/testing: <...> I guess it is not showing them as separate lanes because there they are not in the same place (one is in remotes). @schacon is there a flag to have them show as 2 lanes next to each other?
your syntax main..origin/testing is saying "everything in origin/testing, excluding everything in main (i.e. everything in origin/testing, up until merge-base with main).
what you want is: to see unique commits from both (symmetric difference). the syntax is main...origin/testing (note the 3 dots instead of 2). see "SPECIFYING RANGES" in man gitrevisions.
to visualize, could do something like:
git log --oneline --show-linear-break --left-right main...origin/testing
okay, just got some help from Scott - this one gave what i wanted git log --graph --oneline $(git show-ref refs/gitbutler/testing) origin/testing ^origin/main:
* 884e7d0 (origin/testing) Commit from remote 2
| * dafd306 (testing) Commit 3
|/
* c0a106e Commit from remote
* 250db6c Commit 2
* fdd15ea Commit 1
@PavelLaptev FYI, this graph will never have more than two tips (the diverged case, state 4)
@krlvi got it. Thank you for the explanation. I'll prepare some drafts :-)
Drafts in Figma https://www.figma.com/file/FbeLt0yjY9RiNn8MXUXsYs/Client-Design?type=design&node-id=2100%3A42661&mode=design&t=lerXeTmJGwqsI4gg-1
We can show additional info on hover for the commit card and the avatar:
i really like these drafts! as we discussed irl - we could explore showing in a subtle way one more "node" in the graph for the base of this branch. This is less important than the other things so it can be more subtle, but it could be nice to be able to hover on it and see what was the base here.
But now that I am thinking about it, this would be duplicated for all vbranches in the workspace.... so perhaps this does not belong in the lane, maybe it needs to be somehow outside?
I really like this format, but there is something that we do now that is not reflected here that could be a bit confusing, which is indicate that the upstream-only commits are not incorporated into our working directory. In our current UI, upstream-only commits are above the uncommitted changes and everything those changes depend on (ie, are merged locally) are below that block.
We should indicate this somehow. here, you are interspersing the commits by time of commit, but I don't think that's nearly as important as wether it's merged into our working directory or it's remote only (ie, pending).
Perhaps we force all the upstream work to the top and have a dividing line, or even move them again above the uncommitted changes section. I'm not sure what works better, but they should be a removed block that is clearly unincorporated.
@schacon I was thinking that the red/orange color, together with the the absence of a merge of the two tracks indicates upstream only. Do you mean always keeping the upstream bit on the top to further highlight that like this mockup 👇 ?
I think one less nice thing in this arrangement is that newest commits (one created locally) wont be rendered on the top but maybe thats ok?
Exactly. I think since there's three colors, intermixing them would be confusing as to what is in your working directory and what is not. The upstream ones still need to be integrated and that should be clear. Also, we'll need a UI for that (merge/rebase these), which we're missing here. Those upstream commits are a really different animal and this interface makes them seem equivalent.
Also, we'll need a UI for that (merge/rebase these), which we're missing here.
wouldn't the button below cover those cases? it changes it's mode (merge upstream / [force]push) with an icon matching the color + highlighting the commits to be merged / pushed on hover
Oh sorry, I missed that. Yeah, that could work.
Though, can we do a mockup where we have "merge upstream" directly under the upstream group and "push to remote" under the local group?
I would also be interested in seeing that. But I wonder if we could somehow do it with buttons that are smaller than those big full-width ones, somehow it feels that they break the continuity / flow for me when they are in between other stuff (like in the current implementation)
Something like this (sorry for the horrible photoshop):
We should always group these together, and it seems a bit clearer that there is an action that is performed on that specific group. Also, there are then fewer dropdown options and we can keep the last one easily. Like, the top would be either "Merge" or "Rebase" and the user would probably always want that option. The locals would either be "push" or "open PR" (if no PR yet) and again would always want to default to that.
btw, I really like this where it is a single line, but it bends for the local ones:
It's a nice way to visually distinguish these. Also, we should remember that most of the time, this is all that will be there, there will much more rarely be upstream work.
Groups are cool, but instead of performing multiple actions on each group, let's focus on the most suitable action for the situation.
For instance, if there are upstream commits, we wouldn't recommend a "force push" (you also can't "push to remote" in this case) as the immediate action; instead, the preferable action would be to merge first. Let's explore using a dropdown menu for this purpose, similar to the concept in GitHub Desktop. It helps by suggesting the most common actions, making workflows smoother.
Let me make some new drafts 🙂 I'll try to make something today.
There is one interesting state i just remembered of which would be cool if we can represent nicely - the case when a developer chooses to rebase the branch and they have previously pushed. In this case, the commit content is the same but the hashes are different due to the different base locally and on the remote.
It could be cool if we can communicate that without showing it as a divergence. Or maybe divergence is the only correct way of showing it?
Divergence in this case is the only correct way to show it. What might be valuable is to show a partial sha in these cases (or maybe even generally?)
Divergence in this case is the only correct way to show it. What might be valuable is to show a partial sha in these cases (or maybe even generally?)
In this case, if i have 10 commits that I have already pushed, and then choose to rebase my working directory, until I (force) push, I will have 20 commits on screen, with the upstream ones shown on top, right? I wonder if we can improve on that situation.
Btw, what is a partial sha?
maybe if we can match on identical message (or we could be spiffy and extract a patch-id from each, but there are corner cases that fuck both of those approaches - commit messages are generally fine and fast), we could do some viz where we collapse the rebased ones and mostly show the new ones. I think this is rather common, esp the more we add rebasing methods.
Btw, what is a partial sha?
Just showing the first 6-8 chars, rather than all 40 chars.