tpope / vim-fugitive

fugitive.vim: A Git wrapper so awesome, it should be illegal
https://www.vim.org/scripts/script.php?script_id=2975
20.04k stars 1.01k forks source link

:Gblame 'u' shortcut to remove current patch #112

Closed ramses0 closed 13 years ago

ramses0 commented 13 years ago

Maybe there is a better way to do this, but I'm dealing with a project that is pretty old with lots of authors, lots of commits (especially old, big svn-ish commits, 1k+ line diffs using git show HEAD~1000 | wc -l).

I'll see a :Gblame'd function:

ff1234
ff1234 function foo() {
44abc    if ( xyz == 999 ) {
44abc       alert(1);
44abc    }
ff1234 }
ff1234

I know I can navigate to "44abc" and press "o" to open the diff (that might give me some context clues). I know I can press "enter" to be taken to "the revision of the whole file as it was in 44abc" (which might be 1+ years ago). But sometimes it "feels like" it would be helpful to be able to navigate to "44abc", press "u", and effectively:

git diff 44abc 44abc~1 SomeFile.js | patch && reload SomeFile.js

Ideally with the approximate cursor position / same function? Maybe nearest git-hunk header reference? (http://www.robertames.com/blog.cgi/entries/git-diff-hunk-headers-function-name.html) Maybe requiring a visual selection?

I'm trying to think of a decent algorithm that would make this happen, but nothing is coming right away.

Basically it'd be super helpful to be able to check whether "44abc" was simply splitting the conditional alert from one line to 3, changing the triggering condition, changing from console.log to alert, etc...

Thanks...

tpope commented 13 years ago

Use ~ instead of enter to reblame the file in the ancestor of the commit. If you want to pull that file into the work tree, use :Gwrite, then :Gedit to jump to the work tree version. This does everything except jump to the same spot in the file, which sounds hard. Really hard.

Alternatively, you could use enter to open the newer revision, close the blame window, and :Gdiff ~. (I just fixed a related bug, so pull first.) This might be worth encapsulating in a map. It hadn't occurred to me until now.

What I usually do is o and find the context by hand. If I could find a foolproof way to jump to the appropriate context automatically, I think I would.

tpope commented 13 years ago

I cracked jumping to the right line in the commit. Coupled with be42215003c4955eae8f72fa14af7a371b6088e3, I'm considering this problem solved!

ramses0 commented 13 years ago

Checked into your fixes... I like the "o" being able to take you to the location where the commit occurred, and this is the first time I've been using / exposed to the "~" workflow, which is also pretty awesome.

For a poor-man's "visual" diff, is there a way to do something like ":bp" on both of the splits so I could maybe do ~~~rrr~r~r (maybe w/ - redo mnemonic?). That would require a stack / history so I don't know if it's feasible but it might help.

I also tried this ghetto macro, for use within a basic ":Gblame" split:

   :nmap <F2> 0ye<c-w>l:!git diff <c-r>"..<c-r>"^ -- <c-r>% \| patch -p1        [[[[<cr> ... if you're brave ;-)]]]]

...this is "as ugly as" it looks ... patches in aggregate can fail (of course), but if there were a way to limit it to just the "patch-blob" adjacent to the cursor, that gets closer and closer to the more localized history-browsing that I'm looking for.

I also tried combining that with :Gdiff, with somewhat comical effect:

 :nmap <F2> 0ye<c-w>l:Gdiff <c-r>"<cr><c-w>h<c-w>h:q!<cr>

...it (kindof) opens up an extra buffer with the diff in question, but doesn't work very well.

Finally, after messing with a combination of some of this stuff... PLEASE try the following in a :Gblame window (It works "best-ish"):

:nmap <F2> o<c-w>p~

If you look into marking the "o" window as a preview, then you don't get the whole "stacking" effect as you go backwards and backwards in time. (I dummied some stuff up in my local copy but my vimscript ability is "meh" at best so I couldn't get something that you could just pull and try).

1493,1494d1492 ... 'p' == "o, but in a preview window ... 'z' == try to repro that final macro by stringing together the "real" vimscript commands
<         nnoremap <buffer> <silent> p    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." pedit")<CR>
<         nnoremap <buffer> <silent> z    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." pedit")<CR> \| :<C-U>exe wincmd p \| :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1521,1523d1518  ... this is right after the "execute cmd" line b/c preview window doesn't get focus by default
<   if a:cmd =~# 'pedit'
<     wincmd P
<   endif

It appears as though maybe there is eventually some "skew" w.r.t. running multiple "~" commands in the :Gblame window?

Anyway, thanks for your attention to this. I'm really trying to dig into this use case and see if there is something worthy in there or if it will eventually be something to drop.

tpope commented 13 years ago

A lot here. I'm only going to reply to part right now.

I like the p map, but it raises some questions. pedit normally leaves the focus in the original window, and I'd like to preserve that semantic. But it seems rather silly to use the precious few lines of preview window on a few lines of diff out of context, but require the user to explicitly focus it to actually use it as intended. So do we leave it scrolled to the top instead? I think that's my vote.

Side note: Use git apply not patch -p1. It handles edge cases much more gracefully.

ramses0 commented 13 years ago

The "p" map was not really intended to be a separate mapping, but instead simply not to stomp your "correct" "o" map.

I would suggest trying to simply "tag" the "o" window as a preview window and keep the auto-cursor-jumping to the change and keep the behaviour of putting a user's keyboard focus into the split and do something to make the preview window "pretty big" like the 'o' window is.

That means make it:

     nnoremap <buffer> <silent> o    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." pedit")<CR>
     ...
     if a:cmd =~# 'pedit'
       wincmd P
       wincmd +
       wincmd +
       wincmd +
       wincmd +
       wincmd +
     endif

I think keeping 99% of the same semantics of the current 'o' because that is far more "comfortable" behavior. The idea to me is that 'o' works great as it is, but the idea of "stacking 'o's" is almost never what you want. Or maybe you're right ... 'o' does a real window, 'p' does 'o' but in a preview window and then the macro degenerates into p~p~p~... to visually walk back in time.

In any case, just try this mapping with your code "as-is" and you'll see why I started messing with trying to make the "o" window a preview window instead of "real".

:nmap <F2> o<c-w>p~

...it opens the diff under your cursor, with the +/- all highlighted, and reblames ~ the section of code "as it was during the diff".

The problem is if you bang that macro multiple times it gets progressively less and less useful b/c the 'o' windows stack. Whereas if you were to use the new hypothetical 'p' command the 'o' window basically gets replaced, which I think is almost always the behaviour you want.

Actually, I think you're convincing me... o == as-is, p == o but in a preview window (keep cursor in Gblame window but visually do all the same stuff as 'o'), and maybe ! == p~ ... I think that's a tractable problem / behaviour set for me to work through, I'll see if I can get a patch / pull request worked up and send it your way.