vim-signify/autoload/sy/repo.vim
Marco Hinz ea6db3c7df Improve directory changing
On Windows, Vim and Nvim can't work with mklink'ed paths. Issue an error message
and bail out before starting the actual job when the directory can't be changed.

Fixes https://github.com/mhinz/vim-signify/issues/279
2018-12-20 23:28:01 +01:00

503 lines
15 KiB
VimL

" vim: et sw=2 sts=2
scriptencoding utf-8
" Function: #detect {{{1
function! sy#repo#detect() abort
for vcs in s:vcs_list
let b:sy.detecting += 1
call sy#repo#get_diff_start(vcs)
endfor
endfunction
" Function: s:callback_nvim_stdout{{{1
function! s:callback_nvim_stdout(_job_id, data, _event) dict abort
if empty(self.stdoutbuf) || empty(self.stdoutbuf[-1])
let self.stdoutbuf += a:data
else
let self.stdoutbuf = self.stdoutbuf[:-2]
\ + [self.stdoutbuf[-1] . get(a:data, 0, '')]
\ + a:data[1:]
endif
endfunction
" Function: s:callback_nvim_exit {{{1
function! s:callback_nvim_exit(_job_id, exitval, _event) dict abort
call s:job_exit(self.bufnr, self.vcs, a:exitval, self.stdoutbuf)
endfunction
" Function: s:callback_vim_stdout {{{1
function! s:callback_vim_stdout(_job_id, data) dict abort
let self.stdoutbuf += [a:data]
endfunction
" Function: s:callback_vim_close {{{1
function! s:callback_vim_close(channel) dict abort
let job = ch_getjob(a:channel)
while 1
if job_status(job) == 'dead'
let exitval = job_info(job).exitval
break
endif
sleep 10m
endwhile
call s:job_exit(self.bufnr, self.vcs, exitval, self.stdoutbuf)
endfunction
" Function: s:job_exit {{{1
function! s:job_exit(bufnr, vcs, exitval, diff) abort
call sy#verbose('job_exit()', a:vcs)
let sy = getbufvar(a:bufnr, 'sy')
if empty(sy)
call sy#verbose(printf('No b:sy found for %s', bufname(a:bufnr)), a:vcs)
return
elseif !empty(sy.updated_by) && sy.updated_by != a:vcs
call sy#verbose(printf('Signs already got updated by %s.', sy.updated_by), a:vcs)
return
elseif empty(sy.vcs) && sy.active
let sy.detecting -= 1
endif
call sy#repo#get_diff_{a:vcs}(sy, a:exitval, a:diff)
call setbufvar(a:bufnr, 'sy_job_id_'.a:vcs, 0)
endfunction
" Function: sy#get_diff_start {{{1
function! sy#repo#get_diff_start(vcs) abort
call sy#verbose('get_diff_start()', a:vcs)
let job_id = get(b:, 'sy_job_id_'.a:vcs)
" Neovim
if has('nvim')
if job_id
silent! call jobstop(job_id)
endif
let [cmd, options] = s:initialize_job(a:vcs)
let [cwd, chdir] = sy#util#chdir()
call sy#verbose(printf('CMD: %s | CWD: %s', string(cmd), b:sy.info.dir), a:vcs)
try
execute chdir fnameescape(b:sy.info.dir)
catch
echohl ErrorMsg
echomsg 'signify: Switching Changing failed: '. b:sy.info.dir
echohl NONE
return
endtry
let b:sy_job_id_{a:vcs} = jobstart(cmd, extend(options, {
\ 'on_stdout': function('s:callback_nvim_stdout'),
\ 'on_exit': function('s:callback_nvim_exit'),
\ }))
execute chdir fnameescape(cwd)
" Newer Vim
elseif has('patch-7.4.1967')
if type(job_id) != type(0)
silent! call job_stop(job_id)
endif
let [cmd, options] = s:initialize_job(a:vcs)
let [cwd, chdir] = sy#util#chdir()
call sy#verbose(printf('CMD: %s | CWD: %s', string(cmd), getcwd()), a:vcs)
try
execute chdir fnameescape(b:sy.info.dir)
catch
echohl ErrorMsg
echomsg 'signify: Changing directory failed: '. b:sy.info.dir
echohl NONE
return
endtry
let opts = {
\ 'in_io': 'null',
\ 'out_cb': function('s:callback_vim_stdout', options),
\ 'close_cb': function('s:callback_vim_close', options),
\ }
let b:sy_job_id_{a:vcs} = job_start(cmd, opts)
execute chdir fnameescape(cwd)
" Older Vim
else
let diff = split(s:run(a:vcs), '\n')
call sy#repo#get_diff_{a:vcs}(b:sy, v:shell_error, diff)
endif
endfunction
" Function: s:get_diff_end {{{1
function! s:get_diff_end(sy, found_diff, vcs, diff) abort
call sy#verbose('get_diff_end()', a:vcs)
if a:found_diff
if index(a:sy.vcs, a:vcs) == -1
let a:sy.vcs += [a:vcs]
endif
call sy#set_signs(a:sy, a:vcs, a:diff)
else
call sy#verbose('No valid diff found. Disabling this VCS.', a:vcs)
endif
endfunction
" Function: #get_diff_git {{{1
function! sy#repo#get_diff_git(sy, exitval, diff) abort
call sy#verbose('get_diff_git()', 'git')
let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff]
call s:get_diff_end(a:sy, found_diff, 'git', diff)
endfunction
" Function: #get_diff_hg {{{1
function! sy#repo#get_diff_hg(sy, exitval, diff) abort
call sy#verbose('get_diff_hg()', 'hg')
let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff]
call s:get_diff_end(a:sy, found_diff, 'hg', diff)
endfunction
" Function: #get_diff_svn {{{1
function! sy#repo#get_diff_svn(sy, exitval, diff) abort
call sy#verbose('get_diff_svn()', 'svn')
let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff]
call s:get_diff_end(a:sy, found_diff, 'svn', diff)
endfunction
" Function: #get_diff_bzr {{{1
function! sy#repo#get_diff_bzr(sy, exitval, diff) abort
call sy#verbose('get_diff_bzr()', 'bzr')
let [found_diff, diff] = (a:exitval =~ '[012]') ? [1, a:diff] : [0, []]
call s:get_diff_end(a:sy, found_diff, 'bzr', diff)
endfunction
" Function: #get_diff_darcs {{{1
function! sy#repo#get_diff_darcs(sy, exitval, diff) abort
call sy#verbose('get_diff_darcs()', 'darcs')
let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff]
call s:get_diff_end(a:sy, found_diff, 'darcs', diff)
endfunction
" Function: #get_diff_fossil {{{1
function! sy#repo#get_diff_fossil(sy, exitval, diff) abort
call sy#verbose('get_diff_fossil()', 'fossil')
let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff]
call s:get_diff_end(a:sy, found_diff, 'fossil', diff)
endfunction
" Function: #get_diff_cvs {{{1
function! sy#repo#get_diff_cvs(sy, exitval, diff) abort
call sy#verbose('get_diff_cvs()', 'cvs')
let [found_diff, diff] = [0, []]
if a:exitval == 1
for diffline in a:diff
if diffline =~ '^+++'
let [found_diff, diff] = [1, a:diff]
break
endif
endfor
endif
call s:get_diff_end(a:sy, found_diff, 'cvs', diff)
endfunction
" Function: #get_diff_rcs {{{1
function! sy#repo#get_diff_rcs(sy, exitval, diff) abort
call sy#verbose('get_diff_rcs()', 'rcs')
let [found_diff, diff] = a:exitval == 2 ? [0, []] : [1, a:diff]
call s:get_diff_end(a:sy, found_diff, 'rcs', diff)
endfunction
" Function: #get_diff_accurev {{{1
function! sy#repo#get_diff_accurev(sy, exitval, diff) abort
call sy#verbose('get_diff_accurev()', 'accurev')
let [found_diff, diff] = (a:exitval >= 2) ? [0, []] : [1, a:diff]
call s:get_diff_end(a:sy, found_diff, 'accurev', diff)
endfunction
" Function: #get_diff_perforce {{{1
function! sy#repo#get_diff_perforce(sy, exitval, diff) abort
call sy#verbose('get_diff_perforce()', 'perforce')
let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff]
call s:get_diff_end(a:sy, found_diff, 'perforce', diff)
endfunction
" Function: #get_diff_tfs {{{1
function! sy#repo#get_diff_tfs(sy, exitval, diff) abort
call sy#verbose('get_diff_tfs()', 'tfs')
let [found_diff, diff] = a:exitval ? [0, []] : [1, s:strip_context(a:diff)]
call s:get_diff_end(a:sy, found_diff, 'tfs', diff)
endfunction
" Function: #get_stats {{{1
function! sy#repo#get_stats() abort
return exists('b:sy') ? b:sy.stats : [-1, -1, -1]
endfunction
" Function: #debug_detection {{{1
function! sy#repo#debug_detection()
if !exists('b:sy')
echomsg 'signify: I cannot detect any changes!'
return
endif
for vcs in s:vcs_list
let cmd = s:expand_cmd(vcs, g:signify_vcs_cmds)
echohl Statement
echo cmd
echo repeat('=', len(cmd))
echohl NONE
let diff = s:run(vcs)
if v:shell_error
echohl ErrorMsg
echo diff
echohl NONE
else
echo empty(diff) ? "<none>" : diff
endif
echo "\n"
endfor
endfunction
" Function: #diffmode {{{1
function! sy#repo#diffmode(do_tab) abort
execute sy#util#return_if_no_changes()
let vcs = b:sy.updated_by
if !has_key(g:signify_vcs_cmds_diffmode, vcs)
echomsg 'SignifyDiff has no support for: '. vcs
echomsg 'Open an issue for it at: https://github.com/mhinz/vim-signify/issues'
return
endif
let cmd = s:expand_cmd(vcs, g:signify_vcs_cmds_diffmode)
call sy#verbose('SignifyDiff: '. cmd, vcs)
let ft = &filetype
let fenc = &fenc
if a:do_tab
tabedit %
endif
diffthis
let [cwd, chdir] = sy#util#chdir()
try
execute chdir fnameescape(b:sy.info.dir)
leftabove vnew
if has('iconv')
silent put =iconv(system(cmd), fenc, &enc)
else
silent put =system(cmd)
endif
finally
execute chdir fnameescape(cwd)
endtry
silent 1delete
diffthis
set buftype=nofile bufhidden=wipe nomodified
let &filetype = ft
wincmd p
normal! ]czt
endfunction
" Function: s:initialize_job {{{1
function! s:initialize_job(vcs) abort
let vcs_cmd = s:expand_cmd(a:vcs, g:signify_vcs_cmds)
if has('win32')
if has('nvim')
let cmd = &shell =~ 'cmd' ? vcs_cmd : ['sh', '-c', vcs_cmd]
else
let cmd = join([&shell, &shellcmdflag, vcs_cmd])
endif
else
let cmd = ['sh', '-c', vcs_cmd]
endif
let options = {
\ 'stdoutbuf': [],
\ 'vcs': a:vcs,
\ 'bufnr': bufnr('%'),
\ }
return [cmd, options]
endfunction
" Function: s:get_vcs_path {{{1
function! s:get_vcs_path(vcs) abort
return (a:vcs =~# '\v(git|cvs|accurev|tfs)') ? b:sy.info.file : b:sy.info.path
endfunction
" Function: s:expand_cmd {{{1
function! s:expand_cmd(vcs, vcs_cmds) abort
let cmd = a:vcs_cmds[a:vcs]
let cmd = s:replace(cmd, '%f', s:get_vcs_path(a:vcs))
let cmd = s:replace(cmd, '%d', s:difftool)
let cmd = s:replace(cmd, '%n', s:devnull)
return cmd
endfunction
" Function: s:run {{{1
function! s:run(vcs)
let [cwd, chdir] = sy#util#chdir()
try
execute chdir fnameescape(b:sy.info.dir)
let ret = system(s:expand_cmd(a:vcs, g:signify_vcs_cmds))
catch
" This exception message can be seen via :SignifyDebugUnknown.
" E.g. unquoted VCS programs in vcd_cmds can lead to E484.
let ret = v:exception .' at '. v:throwpoint
finally
execute chdir fnameescape(cwd)
return ret
endtry
endfunction
" Function: s:replace {{{1
function! s:replace(cmd, pat, sub)
let parts = split(a:cmd, a:pat, 1)
return join(parts, a:sub)
endfunction
" Function: s:strip_context {{{1
function! s:strip_context(context)
let diff = []
let hunk = []
let state = 0
let lines = a:context
let linenr = 0
while linenr < len(lines)
let line = lines[linenr]
if state == 0
if line =~ "^@@ "
let tokens = matchlist(line, '^@@ -\v(\d+),?(\d*) \+(\d+),?(\d*)')
let old_line = str2nr(tokens[1])
let new_line = str2nr(tokens[3])
let old_count = empty(tokens[2]) ? 1 : str2nr(tokens[2])
let new_count = empty(tokens[4]) ? 1 : str2nr(tokens[4])
let hunk = []
let state = 1
else
call add(diff,line)
endif
let linenr += 1
elseif index([1,2,3],state) >= 0 && index(['\','/'],line[0]) >= 0
let linenr += 1
call add(hunk,line)
elseif state == 1
if line[0] == ' '
let old_line += 1
let new_line += 1
let old_count -= 1
let new_count -= 1
let linenr += 1
else
let old_count_part = 0
let new_count_part = 0
let state = 2
endif
elseif state == 2
if line[0] == '-'
call add(hunk,line)
let old_count_part += 1
let linenr += 1
else
let state = 3
endif
elseif state == 3
if line[0] == '+'
call add(hunk,line)
let new_count_part += 1
let linenr += 1
else
call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part)))
let diff += hunk
let hunk = []
let old_count -= old_count_part
let new_count -= new_count_part
let old_line += old_count_part
let new_line += new_count_part
let state = 1
endif
endif
if state > 0 && new_count <= 0 && old_count <= 0
if len(hunk) > 0
call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part)))
let diff = diff + hunk
let hunk = []
endif
let state = 0
endif
endwhile
if len(hunk) > 0
call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part)))
let diff = diff + hunk
let hunk = []
endif
return diff
endfunction
" Variables {{{1
let s:difftool = get(g:, 'signify_difftool', 'diff')
if executable(s:difftool)
let s:vcs_dict = {
\ 'git': 'git',
\ 'hg': 'hg',
\ 'svn': 'svn',
\ 'darcs': 'darcs',
\ 'bzr': 'bzr',
\ 'fossil': 'fossil',
\ 'cvs': 'cvs',
\ 'rcs': 'rcsdiff',
\ 'accurev': 'accurev',
\ 'perforce': 'p4',
\ 'tfs': 'tf'
\ }
else
call sy#verbose('No "diff" executable found. Disable support for svn, darcs, bzr, fossil.')
let s:vcs_dict = {
\ 'git': 'git',
\ 'hg': 'hg',
\ 'cvs': 'cvs',
\ 'rcs': 'rcsdiff',
\ 'accurev': 'accurev',
\ 'perforce': 'p4',
\ 'tfs': 'tf'
\ }
endif
let s:vcs_list = get(g:, 'signify_vcs_list', [])
if empty(s:vcs_list)
let s:vcs_list = keys(filter(s:vcs_dict, 'executable(v:val)'))
endif
let s:default_vcs_cmds = {
\ 'git': 'git diff --no-color --no-ext-diff -U0 -- %f',
\ 'hg': 'hg diff --color=never --config aliases.diff= --nodates -U0 -- %f',
\ 'svn': 'svn diff --diff-cmd %d -x -U0 -- %f',
\ 'bzr': 'bzr diff --using %d --diff-options=-U0 -- %f',
\ 'darcs': 'darcs diff --no-pause-for-gui --no-unified --diff-opts=-U0 -- %f',
\ 'fossil': 'fossil set diff-command "%d -U 0" && fossil diff --unified -c 0 -- %f',
\ 'cvs': 'cvs diff -U0 -- %f',
\ 'rcs': 'rcsdiff -U0 %f 2>%n',
\ 'accurev': 'accurev diff %f -- -U0',
\ 'perforce': 'p4 info '. sy#util#shell_redirect('%n') . (has('win32') ? ' &&' : ' && env P4DIFF= P4COLORS=') .' p4 diff -du0 %f',
\ 'tfs': 'tf diff -version:W -noprompt -format:Unified %f'
\ }
let s:default_vcs_cmds_diffmode = {
\ 'git': 'git show HEAD:./%f',
\ 'hg': 'hg cat %f',
\ 'svn': 'svn cat %f',
\ 'bzr': 'bzr cat %f',
\ 'darcs': 'darcs show contents -- %f',
\ 'cvs': 'cvs up -p -- %f 2>%n',
\ 'perforce': 'p4 print %f',
\ }
if exists('g:signify_vcs_cmds')
call extend(g:signify_vcs_cmds, s:default_vcs_cmds, 'keep')
else
let g:signify_vcs_cmds = s:default_vcs_cmds
endif
if exists('g:signify_vcs_cmds_diffmode')
call extend(g:signify_vcs_cmds_diffmode, s:default_vcs_cmds_diffmode, 'keep')
else
let g:signify_vcs_cmds_diffmode = s:default_vcs_cmds_diffmode
endif
let s:difftool = sy#util#escape(s:difftool)
let s:devnull = has('win32') || has ('win64') ? 'NUL' : '/dev/null'