altsem / gitu

A TUI Git client inspired by Magit
MIT License
1.7k stars 88 forks source link

Support staging individual lines #69

Closed starcraft66 closed 4 months ago

starcraft66 commented 4 months ago

My favourite magit feature is the ability to individually stage lines. This tool only seems to let me stage stuff down to the hunk and I'd like for it to allow drilling deeper!

Inside magit, you can open a hunk and then enter visual mode and select lines.

justinrubek commented 4 months ago

I am incredibly interested in having some functionality like this implemented. git add -p is an essential part of my workflow so not having an equivalent is one of the only barriers I can see for integrating gitu into my workflow.

I've been casually looking at what it'd take to implement this (although I haven't used magit, so I'm not familiar with how it handles this), although I haven't had much time to look into it.

altsem commented 4 months ago

I had this working in an earlier version. Will try do a write-up soon

altsem commented 4 months ago

So basically, what the old implementation did to achieve this was to look at a Hunk and assemble a new patch from it (that then would be fed into git apply just like any Hunk).

A patch would look like this (this should all be accessible from the Hunk struct in Gitu. Concat the file header, hunk header and all the lines.

diff --git a/src/lib.rs b/src/lib.rs
index 6a433cd..08f79de 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -31,7 +31,8 @@ pub(crate) struct CmdMetaBuffer {

 pub(crate) struct ErrorBuffer(String);

-fn command_args(cmd: &Command) -> Cow<'static, str> {
+// added
+
     iter::once(cmd.get_program().to_string_lossy())
         .chain(cmd.get_args().map(|arg| arg.to_string_lossy()))
         .join(" ")

One would then modify this (just like when modifying a diff in git add -p:

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -31,7 +31,8 @@ pub(crate) struct CmdMetaBuffer {

 pub(crate) struct ErrorBuffer(String);

-fn command_args(cmd: &Command) -> Cow<'static, str> {
+// added
+
     iter::once(cmd.get_program().to_string_lossy())
         .chain(cmd.get_args().map(|arg| arg.to_string_lossy()))
         .join(" ")
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.

Any edits made will invalidate the hunk header, which keeps track of the line start/count of the old/new versions. Could either recalculate this (is there any gain in doing this?), else I noticed there's the --recount flag for git apply that does this for us.

I suppose it could work by adding a new TargetData variant, DiffLine or something. When the stage/unstage actions are invoked on it, it would produce a patch and apply it.

Lines within a Hunk are created here.

There will need to be some mechanism to navigate line-by-line (ctrl-down/n/j & ctr-up/p/k ?).

altsem commented 4 months ago

There's a hack in where diffs are made twice (Once with 'libgit2', and again with 'similar' to highlight). It makes it more difficult to correlate visual lines seen on the display to lines in the actual diff.

Fixing this first would help in staging individual lines. relates to: #49

edit: this has been fixed in master!

altsem commented 4 months ago

This is available over at https://github.com/altsem/gitu/pull/92 if anyone feels like testing it out and giving feedback!

altsem commented 4 months ago

92 is merged, I'll release it. And let's iterate on it with new issues :)