gitbutler icon indicating copy to clipboard operation
gitbutler copied to clipboard

Move line-based changes around (instead of hunk-based changes)

Open koppor opened this issue 1 year ago • 24 comments

Currently, git-butler works with hunks.

In a significant number of times, hunks are too coarse grained for me. Therefore, I like the line-based workings of git gui.

image

(Artificial example to show the context menu)

I would love if git-butler could support line-based drag'n'drop, too.

By that I mean:

Variant 1:

  1. User drags a single line inside a hunk to another virtual branch
  2. git-butler removes that change from the origin branch and places it to the target (virtual) branch

Variant 2:

  1. User marks an area of text inside a hunk. That area is typically 2 to n lines long.
  2. User drag'n'drops this area to another virtual branch
  3. git-butler removes change touched by the area from the origin branch and places it to the target (virtual) branch

koppor avatar Jan 14 '24 12:01 koppor

So, there are two different things here.

One is the ability to stage lines in a hunk for a commit in a vbranch, which is the equivalent of what you're showing here and would both be cool and fairly easily doable.

The other, which you're actually asking for, is the ability to take lines from within a hunk and drag them to another vbranch to commit independently. This is far, far more complicated.

For the first scenario, we're already doing staged commits at a file and hunk level, so technically bringing it down to a line level is relatively simple and we should be able to do that soon.

For the second scenario, it's quite difficult, because we're dealing with multiple branches simultaneously, but in a single working directory. Which means that if we take lines of a hunk and split them into two branches and commit them independently, then those branches will by definition conflict with each other.

For example, let's assume that we have a Gemfile and we've added two new gems right next to each other:

diff --git a/butler/Gemfile b/butler/Gemfile
index 4ea7360d..2389e983 100644
--- a/butler/Gemfile
+++ b/butler/Gemfile
@@ -27,6 +27,8 @@ gem "puma", "~> 5.6"
 gem "pg", "~> 1.1"
 gem "redis", "~> 4.0"
 gem "paper_trail"
+gem "mysql2"
+gem "grit"

Let's say they're unrelated and we want to have one line in Branch A and the other in Branch B. We can certainly do this, but the tree that we commit on Branch A will just add the single line gem "mysql2" to line 30 and the tree we commit to Branch B will add the single line gem "grit" to line 30 as well.

This means that when we try to merge them on GitHub or whatever, they have to conflict.

Any hunk that we split into different virtual branches in this manner will always conflict when we write trees out from them and then try to merge them again later, because they're in the same hunk space.

Now, we can be clever about it, because we started from the merged solution and extracted conflicting branch states from it (ie, I know that "mysql2" comes before "grit", because we began with that state), so in theory we can keep the resolved state in a rerere style data structure, but GitHub cannot. So when we write the trees for these branches and push them to GitHub, it's difficult for us to help resolve the inevitable merge conflict that will 100% happen once one of these branches is merged.

Now, that's just for a simple example - two lines in a file where it doesnt matter which order they actually go in. But what if you try to drag lines from the middle of a newly written function into two different branches? Maybe it sounds dumb, but it could be done in a way that is difficult to understand the ramifications because you have the merge product on disk and not the weird trees that we're writing out due to splitting the hunk strangely.

In any case, we've been thinking about this a lot and we definitely want to make it work, but it will take some cleverness to make it work nicely. This is to say, probably not a quick fix in the next few weeks, it's a logical, structural issue that is fundamental to dealing with multiple, simultaneously applied branches.

schacon avatar Jan 14 '24 21:01 schacon

OK, so I found the long ass writeup I did 5 months ago on this exact topic and it turns out that I put it in a standalone private repo for "writeups" but it's the only one I ever put there, so I figured I would just make the repo public to share the writeup with you: https://github.com/gitbutlerapp/writeups/blob/main/230901-sc-vb-sub-hunks.md

schacon avatar Jan 14 '24 21:01 schacon

gitx does a good job of this (but it does not have GitButler's virtual branches). Both of those features together would be a-mazing.

Here's a screen recording.

https://github.com/gitbutlerapp/gitbutler/assets/729918/e360f42f-77ad-417e-8580-f170e277ac16

ziadsawalha avatar Apr 09 '24 08:04 ziadsawalha

Any hunk that we split into different virtual branches in this manner will always conflict when we write trees out from them and then try to merge them again later, because they're in the same hunk space.

@schacon - would it simplify the problem if the the two lines were split into two virtual branches where the second branch has the first as the parent? So you maintain the sequence of changes in commits (not a rerere?).

In your example, it would mean that I take the hunk and put it in a virtual branch (branch A). Then I take part of it (ex +gem "mysql2") and move it into a new virtual branch (branch B) that depends on branch A.

That's a fair ask form me as a user... to have me order my changes into a sequence.

ziadsawalha avatar Apr 09 '24 08:04 ziadsawalha

@ziadsawalha we've talked about having vbranches be stacked, which is I think basically what you're asking. Then yes, this becomes much much easier. But then you can't merge branch B independently from branch A, which is a property we want vbranches to have.

The technical solution is not actually super complicated. rerere does this type of thing and we're starting from the resolution, so it's somewhat simple, it's just that we haven't tackled the implementation yet.

schacon avatar Apr 10 '24 08:04 schacon

Both these features would be great, but

One is the ability to stage lines in a hunk for a commit in a vbranch, which is the equivalent of what you're showing here and would both be cool and fairly easily doable.

+1 especially to committing individual lines.

slingshotsys avatar Apr 12 '24 23:04 slingshotsys

This means that when we try to merge them on GitHub or whatever, they have to conflict.

In case the file is NOT flagged with merge=union in .gitattributes. See https://git-scm.com/docs/gitattributes#_built_in_merge_drivers for details.

I use merge=union for CHANGELOG.md files.

koppor avatar Apr 17 '24 10:04 koppor

I wonder, since git butler already takes the view that the work space is synthetic, the result of projecting many actual branches into a single workspace, whether one might consider the edit sequence in each branch to be a sequence of CRDTs.

Separately, a merge model inspired by Darcs would likely do what is desired here.

Or another alternative, tracking branch evolution the way topogit or loom do it, with explicit merging - that can be stripped when doing a different projection (e.g. to make a PR).

rbtcollins avatar Jul 21 '24 20:07 rbtcollins

One is the ability to stage lines in a hunk for a commit in a vbranch, which is the equivalent of what you're showing here and would both be cool and fairly easily doable.

I just wanted to add a +1 vote to this feature. I've been experimenting with GitButler, and the biggest missing feature for my workflow right now is the ability to stage individual lines for committing. I use the GitHub app primarily for this purpose, because staging lines from CLI is just too clunky.

JPry avatar Jul 25 '24 13:07 JPry

. I use the GitHub app primarily for this purpose, because staging lines from CLI is just too clunky.

Please try git-gui&. On macOS, you need to install it via brew install git-gui. See https://superuser.com/a/1522414. It is a very lean and functional UI for Git. Line-based commits work very easy - see my issue description for a screenshot. I was recommended Git Kraken for that, too. However, I like the virtual branches concept of git butler and really want to work with that

koppor avatar Jul 28 '24 11:07 koppor

Related: The label for "Discard" is misleading. I selected some text and then hit "Discard".

Expectation: Only the selected text (or line) is discarded

Instead: The whole hunk is discarded:

grafik


git gui distinguishes here very well:

grafik

(Note that they used "Revert" instead of "Discard". I think "Revert" is the more clear term. This also somehow refs https://github.com/gitbutlerapp/gitbutler/issues/2644)

koppor avatar Aug 01 '24 09:08 koppor

Thanks for sharing this insight, I can also see how git-gui better communicates what would happen, and all that without overcomplicating it.

Byron avatar Aug 01 '24 17:08 Byron