2019-05-21 06:38:28 +00:00
|
|
|
|
" vim: set noexpandtab :miv "
|
|
|
|
|
" ┌─────────────────┐ "
|
|
|
|
|
" └─┬─┬───┬─┬───┬─┬─┘ "
|
|
|
|
|
" │ │ │ │ │ │ "
|
|
|
|
|
" │ │ │ │ │ │ "
|
|
|
|
|
" ┌─┴─┴───┴─┴───┴─┴─┐ "
|
|
|
|
|
" ┌┘ Git Stuff └┐ "
|
|
|
|
|
" └───────────────────┘ "
|
2020-08-17 11:43:24 +00:00
|
|
|
|
augroup git
|
|
|
|
|
|
2018-11-19 18:45:13 +00:00
|
|
|
|
" Find the root of a git repository
|
2018-11-19 15:34:37 +00:00
|
|
|
|
function! s:gitroot()
|
2018-11-19 18:45:13 +00:00
|
|
|
|
let l:ret = substitute(system('git rev-parse --show-toplevel'), '\n\_.*', '', '')
|
2018-11-19 15:34:37 +00:00
|
|
|
|
if v:shell_error
|
2018-11-19 18:45:13 +00:00
|
|
|
|
throw l:ret
|
2018-11-19 15:34:37 +00:00
|
|
|
|
else
|
2018-11-19 18:45:13 +00:00
|
|
|
|
return l:ret
|
2018-11-19 15:34:37 +00:00
|
|
|
|
end
|
|
|
|
|
endf
|
|
|
|
|
|
2019-11-18 12:20:55 +00:00
|
|
|
|
function! s:cd_git_root(path)
|
2019-11-18 13:01:16 +00:00
|
|
|
|
let l:path = fnamemodify(expand(a:path), ':p')
|
2018-11-19 18:45:13 +00:00
|
|
|
|
let l:wd = getcwd()
|
|
|
|
|
if isdirectory(l:path)
|
|
|
|
|
exec 'cd '.a:path
|
|
|
|
|
elseif filereadable(l:path)
|
|
|
|
|
exec 'cd '.fnamemodify(l:path, ':h')
|
|
|
|
|
else
|
2019-11-18 13:01:16 +00:00
|
|
|
|
throw 'Invalid Path'
|
2018-11-19 18:45:13 +00:00
|
|
|
|
endif
|
|
|
|
|
let l:ret = substitute(system('git rev-parse --show-toplevel'), '\n\_.*', '', '')
|
|
|
|
|
if v:shell_error
|
|
|
|
|
exec 'cd '.l:wd
|
2019-11-18 13:01:16 +00:00
|
|
|
|
throw 'Not a git repo!'
|
2018-11-19 18:45:13 +00:00
|
|
|
|
else
|
2019-11-18 12:20:55 +00:00
|
|
|
|
exec 'cd '.l:ret
|
2018-11-19 18:45:13 +00:00
|
|
|
|
return l:ret
|
|
|
|
|
end
|
|
|
|
|
endf
|
|
|
|
|
|
2020-08-17 12:34:10 +00:00
|
|
|
|
function! s:init_file()
|
|
|
|
|
if !exists("b:git_original_file")
|
|
|
|
|
let l:git_original_file = substitute(expand("%"), "\\\\", "/", "g")
|
|
|
|
|
let l:git_revision_hash = system("git")
|
|
|
|
|
end
|
|
|
|
|
endfun
|
|
|
|
|
|
2020-02-07 13:14:19 +00:00
|
|
|
|
function! s:previous_commit()
|
2020-02-07 13:25:38 +00:00
|
|
|
|
" TODO: Refactor this block into s:git_init_buffer() and set buffer
|
|
|
|
|
" variables instead.
|
2020-02-07 13:14:19 +00:00
|
|
|
|
if exists("b:git_original_file") " Is this already a file@revision buffer?
|
|
|
|
|
let l:fname = b:git_original_file
|
|
|
|
|
let l:revision = b:git_revision_hash
|
|
|
|
|
else
|
|
|
|
|
let l:fname = substitute(expand("%"), "\\\\", "/", "g")
|
|
|
|
|
let l:revision = 'HEAD'
|
|
|
|
|
end
|
|
|
|
|
let l:commit = system('git log --format="%h" -1 '.l:revision.'~1 '.l:fname)
|
|
|
|
|
|
|
|
|
|
return substitute(l:commit, "\n", "", "")
|
|
|
|
|
endfun
|
|
|
|
|
|
|
|
|
|
function! s:next_commit()
|
2020-02-07 13:25:38 +00:00
|
|
|
|
" TODO: See previous_commit()
|
2020-02-07 13:14:19 +00:00
|
|
|
|
if exists("b:git_original_file") " Is this already a file@revision buffer?
|
|
|
|
|
let l:fname = b:git_original_file
|
|
|
|
|
let l:revision = b:git_revision_hash
|
|
|
|
|
else
|
|
|
|
|
let l:fname = substitute(expand("%"), "\\\\", "/", "g")
|
|
|
|
|
let l:revision = 'HEAD'
|
|
|
|
|
end
|
|
|
|
|
let l:commit = system('git log --format="%h" '.l:revision.'..HEAD '.l:fname.' | tail -2 | head -1')
|
|
|
|
|
|
|
|
|
|
return substitute(l:commit, "\n", "", "")
|
|
|
|
|
endfun
|
|
|
|
|
|
2018-11-19 15:34:37 +00:00
|
|
|
|
function! s:git_first()
|
2020-02-07 13:25:38 +00:00
|
|
|
|
" FIXME: Broken after removing git_history;
|
|
|
|
|
" see TODO in previous_commit()
|
2018-11-19 15:34:37 +00:00
|
|
|
|
if &modified
|
2018-11-19 18:45:13 +00:00
|
|
|
|
throw "File has unsaved modifications!"
|
2018-11-19 15:34:37 +00:00
|
|
|
|
end
|
2018-11-19 18:45:13 +00:00
|
|
|
|
call s:file_at_revision(get(s:git_history(), -1))
|
2018-11-19 15:34:37 +00:00
|
|
|
|
endfun
|
|
|
|
|
|
|
|
|
|
function! s:git_last()
|
2020-02-07 13:25:38 +00:00
|
|
|
|
" FIXME: Broken after removing git_history;
|
|
|
|
|
" see TODO in previous_commit()
|
2018-11-19 15:34:37 +00:00
|
|
|
|
if &modified
|
2018-11-19 18:45:13 +00:00
|
|
|
|
throw "File has unsaved modifications!"
|
2018-11-19 15:34:37 +00:00
|
|
|
|
end
|
2018-11-19 18:45:13 +00:00
|
|
|
|
call s:file_at_revision(get(s:git_history(), 1, "HEAD"))
|
2018-11-19 15:34:37 +00:00
|
|
|
|
endfun
|
|
|
|
|
|
|
|
|
|
function! s:git_info()
|
|
|
|
|
if !exists("b:git_revision_hash") || !exists("b:git_original_file")
|
2019-11-18 12:36:08 +00:00
|
|
|
|
echom "Working copy or not in any repo"
|
|
|
|
|
return 0
|
2018-11-19 15:34:37 +00:00
|
|
|
|
end
|
|
|
|
|
echo system("git show --no-patch ".b:git_revision_hash)
|
|
|
|
|
endfun
|
|
|
|
|
|
|
|
|
|
function! s:git_next()
|
2020-02-07 13:14:19 +00:00
|
|
|
|
let l:next = s:next_commit()
|
|
|
|
|
if l:next == ""
|
2020-02-07 13:25:38 +00:00
|
|
|
|
exec 'e! '.b:git_original_file
|
|
|
|
|
echom "No newer versions available! (Loading working copy) 😱"
|
2018-11-19 15:34:37 +00:00
|
|
|
|
else
|
2020-02-07 13:14:19 +00:00
|
|
|
|
call s:file_at_revision(l:next)
|
2018-11-19 15:34:37 +00:00
|
|
|
|
end
|
|
|
|
|
endfun
|
|
|
|
|
|
|
|
|
|
function! s:git_prev()
|
2020-02-07 13:14:19 +00:00
|
|
|
|
let l:next = s:previous_commit()
|
|
|
|
|
if l:next == ""
|
|
|
|
|
echom "No older versions available! 😱"
|
2018-11-19 15:34:37 +00:00
|
|
|
|
else
|
2020-02-07 13:14:19 +00:00
|
|
|
|
call s:file_at_revision(l:next)
|
2018-11-19 15:34:37 +00:00
|
|
|
|
end
|
|
|
|
|
endfun
|
|
|
|
|
|
2020-08-17 12:34:10 +00:00
|
|
|
|
function! s:go_blame()
|
|
|
|
|
if exists("b:blame")
|
|
|
|
|
let l:next = b:blame[min([getcurpos()[1], len(b:blame)])-1]["commit"]
|
|
|
|
|
if exists("b:git_revision_hash") && l:next == b:git_revision_hash
|
|
|
|
|
echom "No older versions available! 😱"
|
|
|
|
|
else
|
|
|
|
|
call s:file_at_revision(l:next)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
throw "Not a git buffer!"
|
|
|
|
|
end
|
|
|
|
|
endfun
|
|
|
|
|
|
2018-11-19 15:34:37 +00:00
|
|
|
|
function! s:file_at_revision(rev)
|
|
|
|
|
let l:pos = getpos(".")
|
|
|
|
|
if exists("b:git_original_file") " Is this already a file@revision buffer?
|
|
|
|
|
let l:fname = b:git_original_file
|
|
|
|
|
let l:ftail = fnamemodify(b:git_original_file, ":t")
|
|
|
|
|
else
|
|
|
|
|
let l:fname = expand("%")
|
|
|
|
|
let l:ftail = expand("%:t")
|
|
|
|
|
end
|
|
|
|
|
let l:fname = substitute(l:fname, "\\\\", "/", "g")
|
|
|
|
|
let l:ftype = &filetype
|
|
|
|
|
|
|
|
|
|
ene!
|
|
|
|
|
set modifiable
|
|
|
|
|
silent exec "file ".l:ftail."@".a:rev
|
2020-02-14 10:03:40 +00:00
|
|
|
|
exec "r!git show ".a:rev.":".system("git ls-files --full-name ".l:fname)
|
2018-11-19 15:34:37 +00:00
|
|
|
|
1,1del
|
|
|
|
|
setl nomodifiable
|
|
|
|
|
setl buftype=nofile
|
|
|
|
|
setl bufhidden=delete
|
|
|
|
|
let &filetype = l:ftype
|
|
|
|
|
|
|
|
|
|
let b:git_original_file = l:fname
|
|
|
|
|
let b:git_revision_hash = a:rev
|
2019-11-18 12:36:08 +00:00
|
|
|
|
|
2020-08-17 12:34:10 +00:00
|
|
|
|
try
|
|
|
|
|
let b:blame=s:git_blame("","")
|
|
|
|
|
catch
|
|
|
|
|
unlet! b:blame
|
|
|
|
|
endtry
|
|
|
|
|
|
2019-11-18 12:36:08 +00:00
|
|
|
|
call setpos('.', l:pos)
|
2018-11-19 15:34:37 +00:00
|
|
|
|
endfun
|
|
|
|
|
|
2020-08-17 11:43:24 +00:00
|
|
|
|
function s:split_blame_entry(idx, entry)
|
|
|
|
|
let l:map = {}
|
|
|
|
|
for l:pair in split(a:entry, "\n")[1:]
|
|
|
|
|
let l:split = match(l:pair, " ")
|
|
|
|
|
let l:map[l:pair[:l:split-1]] = l:pair[l:split+1:]
|
|
|
|
|
endfor
|
|
|
|
|
let l:map["commit"]=a:entry[:match(a:entry, " ")-1]
|
2021-06-09 08:55:14 +00:00
|
|
|
|
let l:map["time"]=strftime("%Y-%m-%d %H:%M:%S", l:map["committer-time"])
|
|
|
|
|
let l:map["date"]=strftime("%Y-%m-%d", l:map["committer-time"])
|
2020-08-27 09:18:03 +00:00
|
|
|
|
if l:map["author"]=="Not Committed Yet"
|
|
|
|
|
let l:map["short"]="(Uncommitted)"
|
|
|
|
|
else
|
|
|
|
|
let l:map["short"]=l:map["commit"][:6]." ".l:map["time"]." ".l:map["author"]
|
|
|
|
|
end
|
2020-08-17 11:43:24 +00:00
|
|
|
|
return l:map
|
|
|
|
|
endfun
|
|
|
|
|
let s:split_blame_entry_ref = funcref("s:split_blame_entry")
|
|
|
|
|
|
2019-05-21 06:38:28 +00:00
|
|
|
|
function! s:git_blame(first, last)
|
2020-08-17 12:34:10 +00:00
|
|
|
|
if exists("b:git_revision_hash")
|
|
|
|
|
let l:revision = b:git_revision_hash
|
|
|
|
|
let l:name = b:git_original_file
|
|
|
|
|
else
|
2020-08-25 07:05:09 +00:00
|
|
|
|
let l:revision = ""
|
2020-08-17 12:34:10 +00:00
|
|
|
|
let l:name = expand("%")
|
|
|
|
|
end
|
|
|
|
|
if a:first.a:last == ""
|
2020-08-25 07:05:09 +00:00
|
|
|
|
let l:command = 'git blame '.l:revision.' --line-porcelain -- '.l:name
|
2020-08-17 12:34:10 +00:00
|
|
|
|
else
|
2020-08-25 07:05:09 +00:00
|
|
|
|
let l:command = 'git blame '.l:revision.' --line-porcelain -L '.a:first.','.a:last.' -- '.l:name
|
2020-08-17 12:34:10 +00:00
|
|
|
|
end
|
2020-08-25 07:05:09 +00:00
|
|
|
|
let l:input = system(l:command)
|
2020-08-17 11:43:24 +00:00
|
|
|
|
if v:shell_error
|
|
|
|
|
throw v:shell_error
|
|
|
|
|
else
|
2020-08-25 07:05:09 +00:00
|
|
|
|
let l:split = split(l:input, '\n\t[^\n]*\n')
|
|
|
|
|
let l:array = map(l:split, s:split_blame_entry_ref)
|
2020-08-17 11:43:24 +00:00
|
|
|
|
return l:array
|
|
|
|
|
end
|
2018-11-19 15:34:37 +00:00
|
|
|
|
endfun
|
|
|
|
|
|
2020-05-18 11:59:53 +00:00
|
|
|
|
function! s:git_root_to_path()
|
|
|
|
|
let l:root = substitute(system('git rev-parse --show-toplevel'), '\n\_.*', '', '')
|
|
|
|
|
if !v:shell_error
|
|
|
|
|
let &path.=','.l:root.'/**'
|
|
|
|
|
end
|
|
|
|
|
endfun
|
|
|
|
|
|
2020-08-17 11:43:24 +00:00
|
|
|
|
function! s:blame_command(what, line1, line2)
|
|
|
|
|
let l:what=tolower(a:what)
|
|
|
|
|
if l:what=="date"
|
|
|
|
|
echom join(uniq(sort(map(<sid>git_blame(a:line1, a:line2), { i,a -> a["date"] }))), ', ')
|
|
|
|
|
elseif l:what=="adate"
|
|
|
|
|
echom join(uniq(sort(map(<sid>git_blame(a:line1, a:line2), { i,a -> a["author"]." @ ".a["date"] }))), ', ')
|
|
|
|
|
elseif l:what=="mail"
|
|
|
|
|
echom join(uniq(sort(map(<sid>git_blame(a:line1, a:line2), { i,a -> a["committer-mail"] }))), ', ')
|
|
|
|
|
elseif l:what=="author" || l:what==""
|
|
|
|
|
echom join(uniq(sort(map(<sid>git_blame(a:line1, a:line2), { i,a -> a["author"] }))), ', ')
|
|
|
|
|
else
|
|
|
|
|
throw "Don't know what '".a:what."' is!"
|
|
|
|
|
end
|
|
|
|
|
endfun
|
|
|
|
|
|
|
|
|
|
au BufReadPost * try | let b:blame=<SID>git_blame("","") | catch | unlet! b:blame | endtry
|
|
|
|
|
au BufWritePost * try | let b:blame=<SID>git_blame("","") | catch | unlet! b:blame | endtry
|
|
|
|
|
|
2020-08-25 07:42:16 +00:00
|
|
|
|
command! -nargs=? SplitBlame exec
|
|
|
|
|
\| exec 'normal m"gg'
|
|
|
|
|
\| set cursorbind scrollbind
|
|
|
|
|
\| vert bel split
|
|
|
|
|
\| exec 'Scratch blame'
|
|
|
|
|
\| set cursorbind scrollbind
|
|
|
|
|
\| exec 'r !git blame #'
|
|
|
|
|
\| 1delete 1
|
|
|
|
|
\| silent %s/ *+.*$//
|
|
|
|
|
\| silent %s/(//
|
|
|
|
|
\| silent %s/^\(\x\{8}\) \(.*\)$/\2 \1/
|
|
|
|
|
\| silent %s/^.*0\{8}$//
|
|
|
|
|
\| call matchadd('Comment', '\x\+$')
|
|
|
|
|
\| call matchadd('Comment', '\d\{4}-\d\{2}-\d\{2} \d\{2}:\d\{2}:\d\{2}')
|
|
|
|
|
\| call matchadd('Todo', <q-args>)
|
|
|
|
|
\| vertical resize 50
|
|
|
|
|
\| exec "normal h"
|
|
|
|
|
\| exec 'normal `"'
|
|
|
|
|
|
2020-08-17 11:43:24 +00:00
|
|
|
|
command! -range -nargs=? Blame call <SID>blame_command(<q-args>, <line1>, <line2>)
|
2019-05-21 06:43:46 +00:00
|
|
|
|
command! -range DBlame !git blame % -L <line1>,<line2>
|
2020-08-17 12:34:10 +00:00
|
|
|
|
command! GitNext call <sid>git_next()
|
2020-08-17 12:54:08 +00:00
|
|
|
|
\| GitInfo
|
2019-11-18 12:20:55 +00:00
|
|
|
|
command! GitPrev call <sid>git_prev()
|
2020-08-17 12:54:08 +00:00
|
|
|
|
\| GitInfo
|
2020-08-17 12:34:10 +00:00
|
|
|
|
command! GitGoBlame call <sid>go_blame()
|
2020-08-17 12:54:08 +00:00
|
|
|
|
\| GitInfo
|
2018-11-19 15:34:37 +00:00
|
|
|
|
command! GitFirst call <sid>git_first() | call s:git_info()
|
|
|
|
|
command! GitLast call <sid>git_last() | call s:git_info()
|
|
|
|
|
command! GitInfo call <sid>git_info()
|
|
|
|
|
command! -nargs=1 GitCheckout call <sid>file_at_revision(<f-args>)
|
2019-11-18 13:01:16 +00:00
|
|
|
|
command! GitRoot call <SID>cd_git_root('%')
|
2019-11-18 12:48:13 +00:00
|
|
|
|
command! GitOrig exec 'e '.b:git_original_file
|
2019-11-18 12:20:55 +00:00
|
|
|
|
command! ShowGitRoot try
|
2020-08-17 12:54:08 +00:00
|
|
|
|
\| echo <sid>gitroot()
|
|
|
|
|
\| catch | echo 'Not a git repository'
|
|
|
|
|
\| endtry
|
2023-10-24 14:45:58 +00:00
|
|
|
|
command! GitDiff set nolist | diffthis | vert bel split | exec "silent GitPrev" | set nolist | diffthis
|
2020-08-17 11:43:24 +00:00
|
|
|
|
|
|
|
|
|
augroup END
|