]>
Commit | Line | Data |
---|---|---|
1 | " fugitive.vim - A Git wrapper so awesome, it should be illegal | |
2 | " Maintainer: Tim Pope <http://tpo.pe/> | |
3 | " Version: 1.2 | |
4 | " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim | |
5 | ||
6 | if exists('g:loaded_fugitive') || &cp | |
7 | finish | |
8 | endif | |
9 | let g:loaded_fugitive = 1 | |
10 | ||
11 | if !exists('g:fugitive_git_executable') | |
12 | let g:fugitive_git_executable = 'git' | |
13 | endif | |
14 | ||
15 | " Utility {{{1 | |
16 | ||
17 | function! s:function(name) abort | |
18 | return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),'')) | |
19 | endfunction | |
20 | ||
21 | function! s:sub(str,pat,rep) abort | |
22 | return substitute(a:str,'\v\C'.a:pat,a:rep,'') | |
23 | endfunction | |
24 | ||
25 | function! s:gsub(str,pat,rep) abort | |
26 | return substitute(a:str,'\v\C'.a:pat,a:rep,'g') | |
27 | endfunction | |
28 | ||
29 | function! s:shellesc(arg) abort | |
30 | if a:arg =~ '^[A-Za-z0-9_/.-]\+$' | |
31 | return a:arg | |
32 | elseif &shell =~# 'cmd' && a:arg !~# '"' | |
33 | return '"'.a:arg.'"' | |
34 | else | |
35 | return shellescape(a:arg) | |
36 | endif | |
37 | endfunction | |
38 | ||
39 | function! s:fnameescape(file) abort | |
40 | if exists('*fnameescape') | |
41 | return fnameescape(a:file) | |
42 | else | |
43 | return escape(a:file," \t\n*?[{`$\\%#'\"|!<") | |
44 | endif | |
45 | endfunction | |
46 | ||
47 | function! s:throw(string) abort | |
48 | let v:errmsg = 'fugitive: '.a:string | |
49 | throw v:errmsg | |
50 | endfunction | |
51 | ||
52 | function! s:warn(str) | |
53 | echohl WarningMsg | |
54 | echomsg a:str | |
55 | echohl None | |
56 | let v:warningmsg = a:str | |
57 | endfunction | |
58 | ||
59 | function! s:shellslash(path) | |
60 | if exists('+shellslash') && !&shellslash | |
61 | return s:gsub(a:path,'\\','/') | |
62 | else | |
63 | return a:path | |
64 | endif | |
65 | endfunction | |
66 | ||
67 | function! s:recall() | |
68 | let rev = s:buffer().rev() | |
69 | if rev ==# ':' | |
70 | return matchstr(getline('.'),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*') | |
71 | endif | |
72 | return rev | |
73 | endfunction | |
74 | ||
75 | function! s:add_methods(namespace, method_names) abort | |
76 | for name in a:method_names | |
77 | let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name) | |
78 | endfor | |
79 | endfunction | |
80 | ||
81 | let s:commands = [] | |
82 | function! s:command(definition) abort | |
83 | let s:commands += [a:definition] | |
84 | endfunction | |
85 | ||
86 | function! s:define_commands() | |
87 | for command in s:commands | |
88 | exe 'command! -buffer '.command | |
89 | endfor | |
90 | endfunction | |
91 | ||
92 | function! s:compatibility_check() | |
93 | if exists('b:git_dir') && exists('*GitBranchInfoCheckGitDir') && !exists('g:fugitive_did_compatibility_warning') | |
94 | let g:fugitive_did_compatibility_warning = 1 | |
95 | call s:warn("See http://github.com/tpope/vim-fugitive/issues#issue/1 for why you should remove git-branch-info.vim") | |
96 | endif | |
97 | endfunction | |
98 | ||
99 | augroup fugitive_utility | |
100 | autocmd! | |
101 | autocmd User Fugitive call s:define_commands() | |
102 | autocmd VimEnter * call s:compatibility_check() | |
103 | augroup END | |
104 | ||
105 | let s:abstract_prototype = {} | |
106 | ||
107 | " }}}1 | |
108 | " Initialization {{{1 | |
109 | ||
110 | function! s:ExtractGitDir(path) abort | |
111 | let path = s:shellslash(a:path) | |
112 | if path =~? '^fugitive://.*//' | |
113 | return matchstr(path,'fugitive://\zs.\{-\}\ze//') | |
114 | endif | |
115 | let fn = fnamemodify(path,':s?[\/]$??') | |
116 | let ofn = "" | |
117 | let nfn = fn | |
118 | while fn != ofn | |
119 | if filereadable(fn . '/.git/HEAD') | |
120 | return s:sub(simplify(fnamemodify(fn . '/.git',':p')),'\W$','') | |
121 | elseif fn =~ '\.git$' && filereadable(fn . '/HEAD') | |
122 | return s:sub(simplify(fnamemodify(fn,':p')),'\W$','') | |
123 | endif | |
124 | let ofn = fn | |
125 | let fn = fnamemodify(ofn,':h') | |
126 | endwhile | |
127 | return '' | |
128 | endfunction | |
129 | ||
130 | function! s:Detect(path) | |
131 | if exists('b:git_dir') && b:git_dir ==# '' | |
132 | unlet b:git_dir | |
133 | endif | |
134 | if !exists('b:git_dir') | |
135 | let dir = s:ExtractGitDir(a:path) | |
136 | if dir != '' | |
137 | let b:git_dir = dir | |
138 | endif | |
139 | endif | |
140 | if exists('b:git_dir') | |
141 | silent doautocmd User Fugitive | |
142 | cnoremap <expr> <buffer> <C-R><C-G> <SID>recall() | |
143 | let buffer = fugitive#buffer() | |
144 | if expand('%:p') =~# '//' | |
145 | call buffer.setvar('&path',s:sub(buffer.getvar('&path'),'^\.%(,|$)','')) | |
146 | endif | |
147 | if stridx(buffer.getvar('&tags'),escape(b:git_dir.'/tags',', ')) == -1 | |
148 | call buffer.setvar('&tags',escape(b:git_dir.'/tags',', ').','.buffer.getvar('&tags')) | |
149 | if &filetype != '' | |
150 | call buffer.setvar('&tags',escape(b:git_dir.'/'.&filetype.'.tags',', ').','.buffer.getvar('&tags')) | |
151 | endif | |
152 | endif | |
153 | endif | |
154 | endfunction | |
155 | ||
156 | augroup fugitive | |
157 | autocmd! | |
158 | autocmd BufNewFile,BufReadPost * call s:Detect(expand('<amatch>:p')) | |
159 | autocmd FileType netrw call s:Detect(expand('<afile>:p')) | |
160 | autocmd VimEnter * if expand('<amatch>')==''|call s:Detect(getcwd())|endif | |
161 | autocmd BufWinLeave * execute getwinvar(+winnr(), 'fugitive_leave') | |
162 | augroup END | |
163 | ||
164 | " }}}1 | |
165 | " Repository {{{1 | |
166 | ||
167 | let s:repo_prototype = {} | |
168 | let s:repos = {} | |
169 | ||
170 | function! s:repo(...) abort | |
171 | let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : s:ExtractGitDir(expand('%:p'))) | |
172 | if dir !=# '' | |
173 | if has_key(s:repos,dir) | |
174 | let repo = get(s:repos,dir) | |
175 | else | |
176 | let repo = {'git_dir': dir} | |
177 | let s:repos[dir] = repo | |
178 | endif | |
179 | return extend(extend(repo,s:repo_prototype,'keep'),s:abstract_prototype,'keep') | |
180 | endif | |
181 | call s:throw('not a git repository: '.expand('%:p')) | |
182 | endfunction | |
183 | ||
184 | function! s:repo_dir(...) dict abort | |
185 | return join([self.git_dir]+a:000,'/') | |
186 | endfunction | |
187 | ||
188 | function! s:repo_tree(...) dict abort | |
189 | if !self.bare() | |
190 | let dir = fnamemodify(self.git_dir,':h') | |
191 | return join([dir]+a:000,'/') | |
192 | endif | |
193 | call s:throw('no work tree') | |
194 | endfunction | |
195 | ||
196 | function! s:repo_bare() dict abort | |
197 | return self.dir() !~# '/\.git$' | |
198 | endfunction | |
199 | ||
200 | function! s:repo_translate(spec) dict abort | |
201 | if a:spec ==# '.' || a:spec ==# '/.' | |
202 | return self.bare() ? self.dir() : self.tree() | |
203 | elseif a:spec =~# '^/' | |
204 | return fnamemodify(self.dir(),':h').a:spec | |
205 | elseif a:spec =~# '^:[0-3]:' | |
206 | return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1] | |
207 | elseif a:spec ==# ':' | |
208 | if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(s:repo().dir())] ==# s:repo().dir('') && filereadable($GIT_INDEX_FILE) | |
209 | return fnamemodify($GIT_INDEX_FILE,':p') | |
210 | else | |
211 | return self.dir('index') | |
212 | endif | |
213 | elseif a:spec =~# '^:/' | |
214 | let ref = self.rev_parse(matchstr(a:spec,'.[^:]*')) | |
215 | return 'fugitive://'.self.dir().'//'.ref | |
216 | elseif a:spec =~# '^:' | |
217 | return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1] | |
218 | elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec)) | |
219 | return self.dir(a:spec) | |
220 | elseif filereadable(s:repo().dir('refs/'.a:spec)) | |
221 | return self.dir('refs/'.a:spec) | |
222 | elseif filereadable(s:repo().dir('refs/tags/'.a:spec)) | |
223 | return self.dir('refs/tags/'.a:spec) | |
224 | elseif filereadable(s:repo().dir('refs/heads/'.a:spec)) | |
225 | return self.dir('refs/heads/'.a:spec) | |
226 | elseif filereadable(s:repo().dir('refs/remotes/'.a:spec)) | |
227 | return self.dir('refs/remotes/'.a:spec) | |
228 | elseif filereadable(s:repo().dir('refs/remotes/'.a:spec.'/HEAD')) | |
229 | return self.dir('refs/remotes/'.a:spec,'/HEAD') | |
230 | else | |
231 | try | |
232 | let ref = self.rev_parse(matchstr(a:spec,'[^:]*')) | |
233 | let path = s:sub(matchstr(a:spec,':.*'),'^:','/') | |
234 | return 'fugitive://'.self.dir().'//'.ref.path | |
235 | catch /^fugitive:/ | |
236 | return self.tree(a:spec) | |
237 | endtry | |
238 | endif | |
239 | endfunction | |
240 | ||
241 | call s:add_methods('repo',['dir','tree','bare','translate']) | |
242 | ||
243 | function! s:repo_git_command(...) dict abort | |
244 | let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir) | |
245 | return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'') | |
246 | endfunction | |
247 | ||
248 | function! s:repo_git_chomp(...) dict abort | |
249 | return s:sub(system(call(self.git_command,a:000,self)),'\n$','') | |
250 | endfunction | |
251 | ||
252 | function! s:repo_git_chomp_in_tree(...) dict abort | |
253 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' | |
254 | let dir = getcwd() | |
255 | try | |
256 | execute cd.'`=s:repo().tree()`' | |
257 | return call(s:repo().git_chomp, a:000, s:repo()) | |
258 | finally | |
259 | execute cd.'`=dir`' | |
260 | endtry | |
261 | endfunction | |
262 | ||
263 | function! s:repo_rev_parse(rev) dict abort | |
264 | let hash = self.git_chomp('rev-parse','--verify',a:rev) | |
265 | if hash =~ '\<\x\{40\}$' | |
266 | return matchstr(hash,'\<\x\{40\}$') | |
267 | endif | |
268 | call s:throw('rev-parse '.a:rev.': '.hash) | |
269 | endfunction | |
270 | ||
271 | call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse']) | |
272 | ||
273 | function! s:repo_dirglob(base) dict abort | |
274 | let base = s:sub(a:base,'^/','') | |
275 | let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n") | |
276 | call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]') | |
277 | return matches | |
278 | endfunction | |
279 | ||
280 | function! s:repo_superglob(base) dict abort | |
281 | if a:base =~# '^/' || a:base !~# ':' | |
282 | let results = [] | |
283 | if a:base !~# '^/' | |
284 | let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"] | |
285 | let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n")) | |
286 | call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base') | |
287 | let results += heads | |
288 | endif | |
289 | if !self.bare() | |
290 | let base = s:sub(a:base,'^/','') | |
291 | let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n") | |
292 | call map(matches,'s:shellslash(v:val)') | |
293 | call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val') | |
294 | call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]') | |
295 | let results += matches | |
296 | endif | |
297 | return results | |
298 | ||
299 | elseif a:base =~# '^:' | |
300 | let entries = split(self.git_chomp('ls-files','--stage'),"\n") | |
301 | call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")') | |
302 | if a:base !~# '^:[0-3]\%(:\|$\)' | |
303 | call filter(entries,'v:val[1] == "0"') | |
304 | call map(entries,'v:val[2:-1]') | |
305 | endif | |
306 | call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base') | |
307 | return entries | |
308 | ||
309 | else | |
310 | let tree = matchstr(a:base,'.*[:/]') | |
311 | let entries = split(self.git_chomp('ls-tree',tree),"\n") | |
312 | call map(entries,'s:sub(v:val,"^04.*\\zs$","/")') | |
313 | call map(entries,'tree.s:sub(v:val,".*\t","")') | |
314 | return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base') | |
315 | endif | |
316 | endfunction | |
317 | ||
318 | call s:add_methods('repo',['dirglob','superglob']) | |
319 | ||
320 | function! s:repo_config(conf) dict abort | |
321 | return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*") | |
322 | endfun | |
323 | ||
324 | function! s:repo_user() dict abort | |
325 | let username = s:repo().config('user.name') | |
326 | let useremail = s:repo().config('user.email') | |
327 | return username.' <'.useremail.'>' | |
328 | endfun | |
329 | ||
330 | function! s:repo_aliases() dict abort | |
331 | if !has_key(self,'_aliases') | |
332 | let self._aliases = {} | |
333 | for line in split(self.git_chomp('config','--get-regexp','^alias[.]'),"\n") | |
334 | let self._aliases[matchstr(line,'\.\zs\S\+')] = matchstr(line,' \zs.*') | |
335 | endfor | |
336 | endif | |
337 | return self._aliases | |
338 | endfunction | |
339 | ||
340 | call s:add_methods('repo',['config', 'user', 'aliases']) | |
341 | ||
342 | function! s:repo_keywordprg() dict abort | |
343 | let args = ' --git-dir='.escape(self.dir(),"\\\"' ").' show' | |
344 | if has('gui_running') && !has('win32') | |
345 | return g:fugitive_git_executable . ' --no-pager' . args | |
346 | else | |
347 | return g:fugitive_git_executable . args | |
348 | endif | |
349 | endfunction | |
350 | ||
351 | call s:add_methods('repo',['keywordprg']) | |
352 | ||
353 | " }}}1 | |
354 | " Buffer {{{1 | |
355 | ||
356 | let s:buffer_prototype = {} | |
357 | ||
358 | function! s:buffer(...) abort | |
359 | let buffer = {'#': bufnr(a:0 ? a:1 : '%')} | |
360 | call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep') | |
361 | if buffer.getvar('git_dir') !=# '' | |
362 | return buffer | |
363 | endif | |
364 | call s:throw('not a git repository: '.expand('%:p')) | |
365 | endfunction | |
366 | ||
367 | function! fugitive#buffer(...) abort | |
368 | return s:buffer(a:0 ? a:1 : '%') | |
369 | endfunction | |
370 | ||
371 | function! s:buffer_getvar(var) dict abort | |
372 | return getbufvar(self['#'],a:var) | |
373 | endfunction | |
374 | ||
375 | function! s:buffer_setvar(var,value) dict abort | |
376 | return setbufvar(self['#'],a:var,a:value) | |
377 | endfunction | |
378 | ||
379 | function! s:buffer_getline(lnum) dict abort | |
380 | return getbufline(self['#'],a:lnum)[0] | |
381 | endfunction | |
382 | ||
383 | function! s:buffer_repo() dict abort | |
384 | return s:repo(self.getvar('git_dir')) | |
385 | endfunction | |
386 | ||
387 | function! s:buffer_type(...) dict abort | |
388 | if self.getvar('fugitive_type') != '' | |
389 | let type = self.getvar('fugitive_type') | |
390 | elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$' | |
391 | let type = 'head' | |
392 | elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == '' | |
393 | let type = 'tree' | |
394 | elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t' | |
395 | let type = 'tree' | |
396 | elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t' | |
397 | let type = 'index' | |
398 | elseif isdirectory(self.spec()) | |
399 | let type = 'directory' | |
400 | elseif self.spec() == '' | |
401 | let type = 'null' | |
402 | else | |
403 | let type = 'file' | |
404 | endif | |
405 | if a:0 | |
406 | return !empty(filter(copy(a:000),'v:val ==# type')) | |
407 | else | |
408 | return type | |
409 | endif | |
410 | endfunction | |
411 | ||
412 | if has('win32') | |
413 | ||
414 | function! s:buffer_spec() dict abort | |
415 | let bufname = bufname(self['#']) | |
416 | let retval = '' | |
417 | for i in split(bufname,'[^:]\zs\\') | |
418 | let retval = fnamemodify((retval==''?'':retval.'\').i,':.') | |
419 | endfor | |
420 | return s:shellslash(fnamemodify(retval,':p')) | |
421 | endfunction | |
422 | ||
423 | else | |
424 | ||
425 | function! s:buffer_spec() dict abort | |
426 | let bufname = bufname(self['#']) | |
427 | return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p')) | |
428 | endfunction | |
429 | ||
430 | endif | |
431 | ||
432 | function! s:buffer_name() dict abort | |
433 | return self.spec() | |
434 | endfunction | |
435 | ||
436 | function! s:buffer_commit() dict abort | |
437 | return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*') | |
438 | endfunction | |
439 | ||
440 | function! s:buffer_path(...) dict abort | |
441 | let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*') | |
442 | if rev != '' | |
443 | let rev = s:sub(rev,'\w*','') | |
444 | else | |
445 | let rev = self.spec()[strlen(self.repo().tree()) : -1] | |
446 | endif | |
447 | return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '') | |
448 | endfunction | |
449 | ||
450 | function! s:buffer_rev() dict abort | |
451 | let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*') | |
452 | if rev =~ '^\x/' | |
453 | return ':'.rev[0].':'.rev[2:-1] | |
454 | elseif rev =~ '.' | |
455 | return s:sub(rev,'/',':') | |
456 | elseif self.spec() =~ '\.git/index$' | |
457 | return ':' | |
458 | elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$' | |
459 | return self.spec()[strlen(self.repo().dir())+1 : -1] | |
460 | else | |
461 | return self.path() | |
462 | endif | |
463 | endfunction | |
464 | ||
465 | function! s:buffer_sha1() dict abort | |
466 | if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$' | |
467 | return self.repo().rev_parse(self.rev()) | |
468 | else | |
469 | return '' | |
470 | endif | |
471 | endfunction | |
472 | ||
473 | function! s:buffer_expand(rev) dict abort | |
474 | if a:rev =~# '^:[0-3]$' | |
475 | let file = a:rev.self.path(':') | |
476 | elseif a:rev =~# '^[-:]/$' | |
477 | let file = '/'.self.path() | |
478 | elseif a:rev =~# '^-' | |
479 | let file = 'HEAD^{}'.a:rev[1:-1].self.path(':') | |
480 | elseif a:rev =~# '^@{' | |
481 | let file = 'HEAD'.a:rev.self.path(':') | |
482 | elseif a:rev =~# '^[~^]' | |
483 | let commit = s:sub(self.commit(),'^\d=$','HEAD') | |
484 | let file = commit.a:rev.self.path(':') | |
485 | else | |
486 | let file = a:rev | |
487 | endif | |
488 | return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','') | |
489 | endfunction | |
490 | ||
491 | function! s:buffer_containing_commit() dict abort | |
492 | if self.commit() =~# '^\d$' | |
493 | return ':' | |
494 | elseif self.commit() =~# '.' | |
495 | return self.commit() | |
496 | else | |
497 | return 'HEAD' | |
498 | endif | |
499 | endfunction | |
500 | ||
501 | call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit']) | |
502 | ||
503 | " }}}1 | |
504 | " Git {{{1 | |
505 | ||
506 | call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)") | |
507 | ||
508 | function! s:ExecuteInTree(cmd) abort | |
509 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' | |
510 | let dir = getcwd() | |
511 | try | |
512 | execute cd.'`=s:repo().tree()`' | |
513 | execute a:cmd | |
514 | finally | |
515 | execute cd.'`=dir`' | |
516 | endtry | |
517 | endfunction | |
518 | ||
519 | function! s:Git(bang,cmd) abort | |
520 | if a:bang | |
521 | return s:Edit('edit',1,a:cmd) | |
522 | endif | |
523 | let git = s:repo().git_command() | |
524 | if has('gui_running') && !has('win32') | |
525 | let git .= ' --no-pager' | |
526 | endif | |
527 | let cmd = matchstr(a:cmd,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=') | |
528 | call s:ExecuteInTree('!'.git.' '.cmd) | |
529 | call fugitive#reload_status() | |
530 | return matchstr(a:cmd,'\v\C\\@<!%(\\\\)*\|\zs.*') | |
531 | endfunction | |
532 | ||
533 | function! s:GitComplete(A,L,P) abort | |
534 | if !exists('s:exec_path') | |
535 | let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','') | |
536 | endif | |
537 | let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")') | |
538 | if a:L =~ ' [[:alnum:]-]\+ ' | |
539 | return s:repo().superglob(a:A) | |
540 | elseif a:A == '' | |
541 | return sort(cmds+keys(s:repo().aliases())) | |
542 | else | |
543 | return filter(sort(cmds+keys(s:repo().aliases())),'v:val[0:strlen(a:A)-1] ==# a:A') | |
544 | endif | |
545 | endfunction | |
546 | ||
547 | " }}}1 | |
548 | " Gcd, Glcd {{{1 | |
549 | ||
550 | function! s:DirComplete(A,L,P) abort | |
551 | let matches = s:repo().dirglob(a:A) | |
552 | return matches | |
553 | endfunction | |
554 | ||
555 | call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :cd<bang> `=s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>)`") | |
556 | call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :lcd<bang> `=s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>)`") | |
557 | ||
558 | " }}}1 | |
559 | " Gstatus {{{1 | |
560 | ||
561 | call s:command("-bar Gstatus :execute s:Status()") | |
562 | ||
563 | function! s:Status() abort | |
564 | try | |
565 | Gpedit : | |
566 | wincmd P | |
567 | nnoremap <buffer> <silent> q :<C-U>bdelete<CR> | |
568 | catch /^fugitive:/ | |
569 | return 'echoerr v:errmsg' | |
570 | endtry | |
571 | return '' | |
572 | endfunction | |
573 | ||
574 | function! fugitive#reload_status() abort | |
575 | let mytab = tabpagenr() | |
576 | for tab in [mytab] + range(1,tabpagenr('$')) | |
577 | for winnr in range(1,tabpagewinnr(tab,'$')) | |
578 | if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index' | |
579 | execute 'tabnext '.tab | |
580 | if winnr != winnr() | |
581 | execute winnr.'wincmd w' | |
582 | let restorewinnr = 1 | |
583 | endif | |
584 | try | |
585 | if !&modified | |
586 | call s:BufReadIndex() | |
587 | endif | |
588 | finally | |
589 | if exists('restorewinnr') | |
590 | wincmd p | |
591 | endif | |
592 | execute 'tabnext '.mytab | |
593 | endtry | |
594 | endif | |
595 | endfor | |
596 | endfor | |
597 | endfunction | |
598 | ||
599 | function! s:StageReloadSeek(target,lnum1,lnum2) | |
600 | let jump = a:target | |
601 | let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*') | |
602 | if f !=# '' | let jump = f | endif | |
603 | let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*') | |
604 | if f !=# '' | let jump = f | endif | |
605 | silent! edit! | |
606 | 1 | |
607 | redraw | |
608 | call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.jump.'\%( (new commits)\)\=\$','W') | |
609 | endfunction | |
610 | ||
611 | function! s:StageDiff(diff) abort | |
612 | let section = getline(search('^# .*:$','bcnW')) | |
613 | let line = getline('.') | |
614 | let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$') | |
615 | if filename ==# '' && section ==# '# Changes to be committed:' | |
616 | return 'Git diff --cached' | |
617 | elseif filename ==# '' | |
618 | return 'Git diff' | |
619 | elseif line =~# '^#\trenamed:' && filename =~# ' -> ' | |
620 | let [old, new] = split(filename,' -> ') | |
621 | execute 'Gedit '.s:fnameescape(':0:'.new) | |
622 | return a:diff.' HEAD:'.s:fnameescape(old) | |
623 | elseif section ==# '# Changes to be committed:' | |
624 | execute 'Gedit '.s:fnameescape(':0:'.filename) | |
625 | return a:diff.' -' | |
626 | else | |
627 | execute 'Gedit '.s:fnameescape('/'.filename) | |
628 | return a:diff | |
629 | endif | |
630 | endfunction | |
631 | ||
632 | function! s:StageDiffEdit() abort | |
633 | let section = getline(search('^# .*:$','bcnW')) | |
634 | let line = getline('.') | |
635 | let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$') | |
636 | let arg = (filename ==# '' ? '.' : filename) | |
637 | if section ==# '# Changes to be committed:' | |
638 | return 'Git! diff --cached '.s:shellesc(arg) | |
639 | elseif section ==# '# Untracked files:' | |
640 | let repo = s:repo() | |
641 | call repo.git_chomp_in_tree('add','--intent-to-add',arg) | |
642 | if arg ==# '.' | |
643 | silent! edit! | |
644 | 1 | |
645 | if !search('^# Change\%(d but not updated\|s not staged for commit\):$','W') | |
646 | call search('^# Change','W') | |
647 | endif | |
648 | else | |
649 | call s:StageReloadSeek(arg,line('.'),line('.')) | |
650 | endif | |
651 | return '' | |
652 | else | |
653 | return 'Git! diff '.s:shellesc(arg) | |
654 | endif | |
655 | endfunction | |
656 | ||
657 | function! s:StageToggle(lnum1,lnum2) abort | |
658 | try | |
659 | let output = '' | |
660 | for lnum in range(a:lnum1,a:lnum2) | |
661 | let line = getline(lnum) | |
662 | let repo = s:repo() | |
663 | if line ==# '# Changes to be committed:' | |
664 | call repo.git_chomp_in_tree('reset','-q') | |
665 | silent! edit! | |
666 | 1 | |
667 | if !search('^# Untracked files:$','W') | |
668 | call search('^# Change','W') | |
669 | endif | |
670 | return '' | |
671 | elseif line =~# '^# Change\%(d but not updated\|s not staged for commit\):$' | |
672 | call repo.git_chomp_in_tree('add','-u') | |
673 | silent! edit! | |
674 | 1 | |
675 | if !search('^# Untracked files:$','W') | |
676 | call search('^# Change','W') | |
677 | endif | |
678 | return '' | |
679 | elseif line ==# '# Untracked files:' | |
680 | call repo.git_chomp_in_tree('add','.') | |
681 | silent! edit! | |
682 | 1 | |
683 | call search('^# Change','W') | |
684 | return '' | |
685 | endif | |
686 | let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (\a\+ [[:alpha:], ]\+)\)\=$') | |
687 | if filename ==# '' | |
688 | continue | |
689 | endif | |
690 | if !exists('first_filename') | |
691 | let first_filename = filename | |
692 | endif | |
693 | execute lnum | |
694 | let section = getline(search('^# .*:$','bnW')) | |
695 | if line =~# '^#\trenamed:' && filename =~ ' -> ' | |
696 | let cmd = ['mv','--'] + reverse(split(filename,' -> ')) | |
697 | let filename = cmd[-1] | |
698 | elseif section =~? ' to be ' | |
699 | let cmd = ['reset','-q','--',filename] | |
700 | elseif line =~# '^#\tdeleted:' | |
701 | let cmd = ['rm','--',filename] | |
702 | else | |
703 | let cmd = ['add','--',filename] | |
704 | endif | |
705 | let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n" | |
706 | endfor | |
707 | if exists('first_filename') | |
708 | call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2) | |
709 | endif | |
710 | echo s:sub(s:gsub(output,'\n+','\n'),'\n$','') | |
711 | catch /^fugitive:/ | |
712 | return 'echoerr v:errmsg' | |
713 | endtry | |
714 | return 'checktime' | |
715 | endfunction | |
716 | ||
717 | function! s:StagePatch(lnum1,lnum2) abort | |
718 | let add = [] | |
719 | let reset = [] | |
720 | ||
721 | for lnum in range(a:lnum1,a:lnum2) | |
722 | let line = getline(lnum) | |
723 | if line ==# '# Changes to be committed:' | |
724 | return 'Git reset --patch' | |
725 | elseif line =~# '^# Change\%(d but not updated\|s not staged for commit\):$' | |
726 | return 'Git add --patch' | |
727 | endif | |
728 | let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$') | |
729 | if filename ==# '' | |
730 | continue | |
731 | endif | |
732 | if !exists('first_filename') | |
733 | let first_filename = filename | |
734 | endif | |
735 | execute lnum | |
736 | let section = getline(search('^# .*:$','bnW')) | |
737 | if line =~# '^#\trenamed:' && filename =~ ' -> ' | |
738 | let reset += [split(filename,' -> ')[1]] | |
739 | elseif section =~? ' to be ' | |
740 | let reset += [filename] | |
741 | elseif line !~# '^#\tdeleted:' | |
742 | let add += [filename] | |
743 | endif | |
744 | endfor | |
745 | try | |
746 | if !empty(add) | |
747 | execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)')) | |
748 | endif | |
749 | if !empty(reset) | |
750 | execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)')) | |
751 | endif | |
752 | if exists('first_filename') | |
753 | silent! edit! | |
754 | 1 | |
755 | redraw | |
756 | call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( (new commits)\)\=\$','W') | |
757 | endif | |
758 | catch /^fugitive:/ | |
759 | return 'echoerr v:errmsg' | |
760 | endtry | |
761 | return 'checktime' | |
762 | endfunction | |
763 | ||
764 | " }}}1 | |
765 | " Gcommit {{{1 | |
766 | ||
767 | call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)") | |
768 | ||
769 | function! s:Commit(args) abort | |
770 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' | |
771 | let dir = getcwd() | |
772 | let msgfile = s:repo().dir('COMMIT_EDITMSG') | |
773 | let outfile = tempname() | |
774 | let errorfile = tempname() | |
775 | try | |
776 | execute cd.'`=s:repo().tree()`' | |
777 | if &shell =~# 'cmd' | |
778 | let command = '' | |
779 | let old_editor = $GIT_EDITOR | |
780 | let $GIT_EDITOR = 'false' | |
781 | else | |
782 | let command = 'env GIT_EDITOR=false ' | |
783 | endif | |
784 | let command .= s:repo().git_command('commit').' '.a:args | |
785 | if &shell =~# 'csh' | |
786 | silent execute '!('.command.' > '.outfile.') >& '.errorfile | |
787 | elseif a:args =~# '\%(^\| \)--interactive\>' | |
788 | execute '!'.command.' 2> '.errorfile | |
789 | else | |
790 | silent execute '!'.command.' > '.outfile.' 2> '.errorfile | |
791 | endif | |
792 | if !has('gui_running') | |
793 | redraw! | |
794 | endif | |
795 | if !v:shell_error | |
796 | if filereadable(outfile) | |
797 | for line in readfile(outfile) | |
798 | echo line | |
799 | endfor | |
800 | endif | |
801 | return '' | |
802 | else | |
803 | let errors = readfile(errorfile) | |
804 | let error = get(errors,-2,get(errors,-1,'!')) | |
805 | if error =~# '\<false''\=\.$' | |
806 | let args = a:args | |
807 | let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[se]|--edit|--interactive)%($| )','') | |
808 | let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','') | |
809 | let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))') | |
810 | let args = '-F '.s:shellesc(msgfile).' '.args | |
811 | if args !~# '\%(^\| \)--cleanup\>' | |
812 | let args = '--cleanup=strip '.args | |
813 | endif | |
814 | if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod | |
815 | keepalt edit `=msgfile` | |
816 | elseif s:buffer().type() ==# 'index' | |
817 | keepalt edit `=msgfile` | |
818 | execute (search('^#','n')+1).'wincmd+' | |
819 | setlocal nopreviewwindow | |
820 | else | |
821 | keepalt split `=msgfile` | |
822 | endif | |
823 | let b:fugitive_commit_arguments = args | |
824 | setlocal bufhidden=delete filetype=gitcommit | |
825 | return '1' | |
826 | elseif error ==# '!' | |
827 | return s:Status() | |
828 | else | |
829 | call s:throw(error) | |
830 | endif | |
831 | endif | |
832 | catch /^fugitive:/ | |
833 | return 'echoerr v:errmsg' | |
834 | finally | |
835 | if exists('old_editor') | |
836 | let $GIT_EDITOR = old_editor | |
837 | endif | |
838 | call delete(outfile) | |
839 | call delete(errorfile) | |
840 | execute cd.'`=dir`' | |
841 | call fugitive#reload_status() | |
842 | endtry | |
843 | endfunction | |
844 | ||
845 | function! s:CommitComplete(A,L,P) abort | |
846 | if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab> | |
847 | let args = ['-C', '-F', '-a', '-c', '-e', '-i', '-m', '-n', '-o', '-q', '-s', '-t', '-u', '-v', '--all', '--allow-empty', '--amend', '--author=', '--cleanup=', '--dry-run', '--edit', '--file=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--template=', '--untracked-files', '--verbose'] | |
848 | return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A') | |
849 | else | |
850 | return s:repo().superglob(a:A) | |
851 | endif | |
852 | endfunction | |
853 | ||
854 | function! s:FinishCommit() | |
855 | let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments') | |
856 | if !empty(args) | |
857 | call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','') | |
858 | return s:Commit(args) | |
859 | endif | |
860 | return '' | |
861 | endfunction | |
862 | ||
863 | augroup fugitive_commit | |
864 | autocmd! | |
865 | autocmd VimLeavePre,BufDelete *.git/COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE') | |
866 | augroup END | |
867 | ||
868 | " }}}1 | |
869 | " Ggrep, Glog {{{1 | |
870 | ||
871 | if !exists('g:fugitive_summary_format') | |
872 | let g:fugitive_summary_format = '%s' | |
873 | endif | |
874 | ||
875 | call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep(<bang>0,<q-args>)") | |
876 | call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Glog :execute s:Log('grep<bang>',<f-args>)") | |
877 | ||
878 | function! s:Grep(bang,arg) abort | |
879 | let grepprg = &grepprg | |
880 | let grepformat = &grepformat | |
881 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' | |
882 | let dir = getcwd() | |
883 | try | |
884 | execute cd.'`=s:repo().tree()`' | |
885 | let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n') | |
886 | let &grepformat = '%f:%l:%m' | |
887 | exe 'grep! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|') | |
888 | let list = getqflist() | |
889 | for entry in list | |
890 | if bufname(entry.bufnr) =~ ':' | |
891 | let entry.filename = s:repo().translate(bufname(entry.bufnr)) | |
892 | unlet! entry.bufnr | |
893 | elseif a:arg =~# '\%(^\| \)--cached\>' | |
894 | let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr)) | |
895 | unlet! entry.bufnr | |
896 | endif | |
897 | endfor | |
898 | call setqflist(list,'r') | |
899 | if !a:bang && !empty(list) | |
900 | return 'cfirst'.matchstr(a:arg,'\v\C[''" ]\zs\|.*') | |
901 | else | |
902 | return matchstr(a:arg,'\v\C[''" ]\|\zs.*') | |
903 | endif | |
904 | finally | |
905 | let &grepprg = grepprg | |
906 | let &grepformat = grepformat | |
907 | execute cd.'`=dir`' | |
908 | endtry | |
909 | endfunction | |
910 | ||
911 | function! s:Log(cmd,...) | |
912 | let path = s:buffer().path('/') | |
913 | if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1 | |
914 | let path = '' | |
915 | endif | |
916 | let cmd = ['--no-pager', 'log', '--no-color'] | |
917 | let cmd += [escape('--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format,'%')] | |
918 | if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"')) | |
919 | if s:buffer().commit() =~# '\x\{40\}' | |
920 | let cmd += [s:buffer().commit()] | |
921 | elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$' | |
922 | let cmd += [s:buffer().path()[5:-1]] | |
923 | endif | |
924 | end | |
925 | let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")') | |
926 | if path =~# '/.' | |
927 | let cmd += ['--',path[1:-1]] | |
928 | endif | |
929 | let grepformat = &grepformat | |
930 | let grepprg = &grepprg | |
931 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' | |
932 | let dir = getcwd() | |
933 | try | |
934 | execute cd.'`=s:repo().tree()`' | |
935 | let &grepprg = call(s:repo().git_command,cmd,s:repo()) | |
936 | let &grepformat = '%f::%m' | |
937 | exe a:cmd | |
938 | finally | |
939 | let &grepformat = grepformat | |
940 | let &grepprg = grepprg | |
941 | execute cd.'`=dir`' | |
942 | endtry | |
943 | endfunction | |
944 | ||
945 | " }}}1 | |
946 | " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1 | |
947 | ||
948 | function! s:Edit(cmd,bang,...) abort | |
949 | if a:cmd !~# 'read' | |
950 | if &previewwindow && getbufvar('','fugitive_type') ==# 'index' | |
951 | wincmd p | |
952 | if &diff | |
953 | let mywinnr = winnr() | |
954 | for winnr in range(winnr('$'),1,-1) | |
955 | if winnr != mywinnr && getwinvar(winnr,'&diff') | |
956 | execute winnr.'wincmd w' | |
957 | close | |
958 | wincmd p | |
959 | endif | |
960 | endfor | |
961 | endif | |
962 | endif | |
963 | endif | |
964 | ||
965 | if a:bang | |
966 | let args = s:gsub(a:0 ? a:1 : '', '\\@<!%(\\\\)*\zs[%#]', '\=s:buffer().expand(submatch(0))') | |
967 | if a:cmd =~# 'read' | |
968 | let git = s:repo().git_command() | |
969 | let last = line('$') | |
970 | silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args) | |
971 | if a:cmd ==# 'read' | |
972 | silent execute '1,'.last.'delete_' | |
973 | endif | |
974 | call fugitive#reload_status() | |
975 | diffupdate | |
976 | return 'redraw|echo '.string(':!'.git.' '.args) | |
977 | else | |
978 | let temp = tempname() | |
979 | let s:temp_files[temp] = s:repo().dir() | |
980 | silent execute a:cmd.' '.temp | |
981 | if a:cmd =~# 'pedit' | |
982 | wincmd P | |
983 | endif | |
984 | let echo = s:Edit('read',1,args) | |
985 | silent write! | |
986 | setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>> | |
987 | if getline(1) !~# '^diff ' | |
988 | setlocal readonly nomodifiable | |
989 | endif | |
990 | if a:cmd =~# 'pedit' | |
991 | wincmd p | |
992 | endif | |
993 | return echo | |
994 | endif | |
995 | return '' | |
996 | endif | |
997 | ||
998 | if a:0 && a:1 == '' | |
999 | return '' | |
1000 | elseif a:0 | |
1001 | let file = s:buffer().expand(a:1) | |
1002 | elseif expand('%') ==# '' | |
1003 | let file = ':' | |
1004 | elseif s:buffer().commit() ==# '' && s:buffer().path('/') !~# '^/.git\>' | |
1005 | let file = s:buffer().path(':') | |
1006 | else | |
1007 | let file = s:buffer().path('/') | |
1008 | endif | |
1009 | try | |
1010 | let file = s:repo().translate(file) | |
1011 | catch /^fugitive:/ | |
1012 | return 'echoerr v:errmsg' | |
1013 | endtry | |
1014 | if a:cmd ==# 'read' | |
1015 | return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.') | |
1016 | else | |
1017 | return a:cmd.' '.s:fnameescape(file) | |
1018 | endif | |
1019 | endfunction | |
1020 | ||
1021 | function! s:EditComplete(A,L,P) abort | |
1022 | return s:repo().superglob(a:A) | |
1023 | endfunction | |
1024 | ||
1025 | function! s:EditRunComplete(A,L,P) abort | |
1026 | if a:L =~# '^\w\+!' | |
1027 | return s:GitComplete(a:A,a:L,a:P) | |
1028 | else | |
1029 | return s:repo().superglob(a:A) | |
1030 | endif | |
1031 | endfunction | |
1032 | ||
1033 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)") | |
1034 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)") | |
1035 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)") | |
1036 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)") | |
1037 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)") | |
1038 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)") | |
1039 | call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)") | |
1040 | ||
1041 | " }}}1 | |
1042 | " Gwrite, Gwq {{{1 | |
1043 | ||
1044 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)") | |
1045 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)") | |
1046 | call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)") | |
1047 | ||
1048 | function! s:Write(force,...) abort | |
1049 | if exists('b:fugitive_commit_arguments') | |
1050 | return 'write|bdelete' | |
1051 | elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != '' | |
1052 | return 'wq' | |
1053 | elseif s:buffer().type() == 'index' | |
1054 | return 'Gcommit' | |
1055 | elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ ' | |
1056 | let filename = getline(4)[6:-1] | |
1057 | setlocal buftype= | |
1058 | silent write | |
1059 | setlocal buftype=nowrite | |
1060 | if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6] | |
1061 | let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec()) | |
1062 | else | |
1063 | let err = s:repo().git_chomp('apply','--cached',s:buffer().spec()) | |
1064 | endif | |
1065 | if err !=# '' | |
1066 | let v:errmsg = split(err,"\n")[0] | |
1067 | return 'echoerr v:errmsg' | |
1068 | elseif a:force | |
1069 | return 'bdelete' | |
1070 | else | |
1071 | return 'Gedit '.fnameescape(filename) | |
1072 | endif | |
1073 | endif | |
1074 | let mytab = tabpagenr() | |
1075 | let mybufnr = bufnr('') | |
1076 | let path = a:0 ? a:1 : s:buffer().path() | |
1077 | if path =~# '^:\d\>' | |
1078 | return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path))) | |
1079 | endif | |
1080 | let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$') | |
1081 | if !always_permitted && !a:force && s:repo().git_chomp_in_tree('diff','--name-status','HEAD','--',path) . s:repo().git_chomp_in_tree('ls-files','--others','--',path) !=# '' | |
1082 | let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)' | |
1083 | return 'echoerr v:errmsg' | |
1084 | endif | |
1085 | let file = s:repo().translate(path) | |
1086 | let treebufnr = 0 | |
1087 | for nr in range(1,bufnr('$')) | |
1088 | if fnamemodify(bufname(nr),':p') ==# file | |
1089 | let treebufnr = nr | |
1090 | endif | |
1091 | endfor | |
1092 | ||
1093 | if treebufnr > 0 && treebufnr != bufnr('') | |
1094 | let temp = tempname() | |
1095 | silent execute '%write '.temp | |
1096 | for tab in [mytab] + range(1,tabpagenr('$')) | |
1097 | for winnr in range(1,tabpagewinnr(tab,'$')) | |
1098 | if tabpagebuflist(tab)[winnr-1] == treebufnr | |
1099 | execute 'tabnext '.tab | |
1100 | if winnr != winnr() | |
1101 | execute winnr.'wincmd w' | |
1102 | let restorewinnr = 1 | |
1103 | endif | |
1104 | try | |
1105 | let lnum = line('.') | |
1106 | let last = line('$') | |
1107 | silent execute '$read '.temp | |
1108 | silent execute '1,'.last.'delete_' | |
1109 | silent write! | |
1110 | silent execute lnum | |
1111 | let did = 1 | |
1112 | finally | |
1113 | if exists('restorewinnr') | |
1114 | wincmd p | |
1115 | endif | |
1116 | execute 'tabnext '.mytab | |
1117 | endtry | |
1118 | endif | |
1119 | endfor | |
1120 | endfor | |
1121 | if !exists('did') | |
1122 | call writefile(readfile(temp,'b'),file,'b') | |
1123 | endif | |
1124 | else | |
1125 | execute 'write! '.s:fnameescape(s:repo().translate(path)) | |
1126 | endif | |
1127 | ||
1128 | if a:force | |
1129 | let error = s:repo().git_chomp_in_tree('add', '--force', file) | |
1130 | else | |
1131 | let error = s:repo().git_chomp_in_tree('add', file) | |
1132 | endif | |
1133 | if v:shell_error | |
1134 | let v:errmsg = 'fugitive: '.error | |
1135 | return 'echoerr v:errmsg' | |
1136 | endif | |
1137 | if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$' | |
1138 | set nomodified | |
1139 | endif | |
1140 | ||
1141 | let one = s:repo().translate(':1:'.path) | |
1142 | let two = s:repo().translate(':2:'.path) | |
1143 | let three = s:repo().translate(':3:'.path) | |
1144 | for nr in range(1,bufnr('$')) | |
1145 | if bufloaded(nr) && !getbufvar(nr,'&modified') && (bufname(nr) == one || bufname(nr) == two || bufname(nr) == three) | |
1146 | execute nr.'bdelete' | |
1147 | endif | |
1148 | endfor | |
1149 | ||
1150 | unlet! restorewinnr | |
1151 | let zero = s:repo().translate(':0:'.path) | |
1152 | for tab in range(1,tabpagenr('$')) | |
1153 | for winnr in range(1,tabpagewinnr(tab,'$')) | |
1154 | let bufnr = tabpagebuflist(tab)[winnr-1] | |
1155 | let bufname = bufname(bufnr) | |
1156 | if bufname ==# zero && bufnr != mybufnr | |
1157 | execute 'tabnext '.tab | |
1158 | if winnr != winnr() | |
1159 | execute winnr.'wincmd w' | |
1160 | let restorewinnr = 1 | |
1161 | endif | |
1162 | try | |
1163 | let lnum = line('.') | |
1164 | let last = line('$') | |
1165 | silent $read `=file` | |
1166 | silent execute '1,'.last.'delete_' | |
1167 | silent execute lnum | |
1168 | set nomodified | |
1169 | diffupdate | |
1170 | finally | |
1171 | if exists('restorewinnr') | |
1172 | wincmd p | |
1173 | endif | |
1174 | execute 'tabnext '.mytab | |
1175 | endtry | |
1176 | break | |
1177 | endif | |
1178 | endfor | |
1179 | endfor | |
1180 | call fugitive#reload_status() | |
1181 | return 'checktime' | |
1182 | endfunction | |
1183 | ||
1184 | function! s:Wq(force,...) abort | |
1185 | let bang = a:force ? '!' : '' | |
1186 | if exists('b:fugitive_commit_arguments') | |
1187 | return 'wq'.bang | |
1188 | endif | |
1189 | let result = call(s:function('s:Write'),[a:force]+a:000) | |
1190 | if result =~# '^\%(write\|wq\|echoerr\)' | |
1191 | return s:sub(result,'^write','wq') | |
1192 | else | |
1193 | return result.'|quit'.bang | |
1194 | endif | |
1195 | endfunction | |
1196 | ||
1197 | " }}}1 | |
1198 | " Gdiff {{{1 | |
1199 | ||
1200 | call s:command("-bang -bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff(<bang>0,<f-args>)") | |
1201 | call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gvdiff :execute s:Diff(0,<f-args>)") | |
1202 | call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gsdiff :execute s:Diff(1,<f-args>)") | |
1203 | ||
1204 | augroup fugitive_diff | |
1205 | autocmd! | |
1206 | autocmd BufWinLeave * if s:diff_window_count() == 2 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) | endif | |
1207 | autocmd BufWinEnter * if s:diff_window_count() == 1 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diffoff() | endif | |
1208 | augroup END | |
1209 | ||
1210 | function! s:diff_window_count() | |
1211 | let c = 0 | |
1212 | for nr in range(1,winnr('$')) | |
1213 | let c += getwinvar(nr,'&diff') | |
1214 | endfor | |
1215 | return c | |
1216 | endfunction | |
1217 | ||
1218 | function! s:diffthis() | |
1219 | if !&diff | |
1220 | let w:fugitive_diff_restore = 'setlocal nodiff noscrollbind' | |
1221 | let w:fugitive_diff_restore .= ' scrollopt=' . &l:scrollopt | |
1222 | let w:fugitive_diff_restore .= &l:wrap ? ' wrap' : ' nowrap' | |
1223 | let w:fugitive_diff_restore .= ' foldmethod=' . &l:foldmethod | |
1224 | let w:fugitive_diff_restore .= ' foldcolumn=' . &l:foldcolumn | |
1225 | diffthis | |
1226 | endif | |
1227 | endfunction | |
1228 | ||
1229 | function! s:diffoff() | |
1230 | if exists('w:fugitive_diff_restore') | |
1231 | execute w:fugitive_diff_restore | |
1232 | unlet w:fugitive_diff_restore | |
1233 | else | |
1234 | diffoff | |
1235 | endif | |
1236 | endfunction | |
1237 | ||
1238 | function! s:diffoff_all(dir) | |
1239 | for nr in range(1,winnr('$')) | |
1240 | if getwinvar(nr,'&diff') | |
1241 | if nr != winnr() | |
1242 | execute nr.'wincmd w' | |
1243 | let restorewinnr = 1 | |
1244 | endif | |
1245 | if exists('b:git_dir') && b:git_dir ==# a:dir | |
1246 | call s:diffoff() | |
1247 | endif | |
1248 | if exists('restorewinnr') | |
1249 | wincmd p | |
1250 | endif | |
1251 | endif | |
1252 | endfor | |
1253 | endfunction | |
1254 | ||
1255 | function! s:buffer_compare_age(commit) dict abort | |
1256 | let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5} | |
1257 | let my_score = get(scores,':'.self.commit(),0) | |
1258 | let their_score = get(scores,':'.a:commit,0) | |
1259 | if my_score || their_score | |
1260 | return my_score < their_score ? -1 : my_score != their_score | |
1261 | elseif self.commit() ==# a:commit | |
1262 | return 0 | |
1263 | endif | |
1264 | let base = self.repo().git_chomp('merge-base',self.commit(),a:commit) | |
1265 | if base ==# self.commit() | |
1266 | return -1 | |
1267 | elseif base ==# a:commit | |
1268 | return 1 | |
1269 | endif | |
1270 | let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit()) | |
1271 | let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit) | |
1272 | return my_time < their_time ? -1 : my_time != their_time | |
1273 | endfunction | |
1274 | ||
1275 | call s:add_methods('buffer',['compare_age']) | |
1276 | ||
1277 | function! s:Diff(bang,...) abort | |
1278 | let split = a:bang ? 'split' : 'vsplit' | |
1279 | if exists(':DiffGitCached') | |
1280 | return 'DiffGitCached' | |
1281 | elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# '' | |
1282 | let nr = bufnr('') | |
1283 | execute 'leftabove '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`' | |
1284 | execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>' | |
1285 | call s:diffthis() | |
1286 | wincmd p | |
1287 | execute 'rightbelow '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`' | |
1288 | execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>' | |
1289 | call s:diffthis() | |
1290 | wincmd p | |
1291 | call s:diffthis() | |
1292 | return '' | |
1293 | elseif a:0 | |
1294 | if a:1 ==# '' | |
1295 | return '' | |
1296 | elseif a:1 ==# '/' | |
1297 | let file = s:buffer().path('/') | |
1298 | elseif a:1 ==# ':' | |
1299 | let file = s:buffer().path(':0:') | |
1300 | elseif a:1 =~# '^:/.' | |
1301 | try | |
1302 | let file = s:repo().rev_parse(a:1).s:buffer().path(':') | |
1303 | catch /^fugitive:/ | |
1304 | return 'echoerr v:errmsg' | |
1305 | endtry | |
1306 | else | |
1307 | let file = s:buffer().expand(a:1) | |
1308 | endif | |
1309 | if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$' | |
1310 | let file = file.s:buffer().path(':') | |
1311 | endif | |
1312 | else | |
1313 | let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/') | |
1314 | endif | |
1315 | try | |
1316 | let spec = s:repo().translate(file) | |
1317 | let commit = matchstr(spec,'\C[^:/]//\zs\x\+') | |
1318 | if s:buffer().compare_age(commit) < 0 | |
1319 | execute 'rightbelow '.split.' `=spec`' | |
1320 | else | |
1321 | execute 'leftabove '.split.' `=spec`' | |
1322 | endif | |
1323 | call s:diffthis() | |
1324 | wincmd p | |
1325 | call s:diffthis() | |
1326 | return '' | |
1327 | catch /^fugitive:/ | |
1328 | return 'echoerr v:errmsg' | |
1329 | endtry | |
1330 | endfunction | |
1331 | ||
1332 | " }}}1 | |
1333 | " Gmove, Gremove {{{1 | |
1334 | ||
1335 | function! s:Move(force,destination) | |
1336 | if a:destination =~# '^/' | |
1337 | let destination = a:destination[1:-1] | |
1338 | else | |
1339 | let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p') | |
1340 | if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('') | |
1341 | let destination = destination[strlen(s:repo().tree('')):-1] | |
1342 | endif | |
1343 | endif | |
1344 | if isdirectory(s:buffer().name()) | |
1345 | " Work around Vim parser idiosyncrasy | |
1346 | let discarded = s:buffer().setvar('&swapfile',0) | |
1347 | endif | |
1348 | let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo()) | |
1349 | if v:shell_error | |
1350 | let v:errmsg = 'fugitive: '.message | |
1351 | return 'echoerr v:errmsg' | |
1352 | endif | |
1353 | let destination = s:repo().tree(destination) | |
1354 | if isdirectory(destination) | |
1355 | let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.') | |
1356 | endif | |
1357 | call fugitive#reload_status() | |
1358 | if s:buffer().commit() == '' | |
1359 | if isdirectory(destination) | |
1360 | return 'edit '.s:fnameescape(destination) | |
1361 | else | |
1362 | return 'saveas! '.s:fnameescape(destination) | |
1363 | endif | |
1364 | else | |
1365 | return 'file '.s:fnameescape(s:repo().translate(':0:'.destination) | |
1366 | endif | |
1367 | endfunction | |
1368 | ||
1369 | function! s:MoveComplete(A,L,P) | |
1370 | if a:A =~ '^/' | |
1371 | return s:repo().superglob(a:A) | |
1372 | else | |
1373 | let matches = split(glob(a:A.'*'),"\n") | |
1374 | call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val') | |
1375 | return matches | |
1376 | endif | |
1377 | endfunction | |
1378 | ||
1379 | function! s:Remove(force) | |
1380 | if s:buffer().commit() ==# '' | |
1381 | let cmd = ['rm'] | |
1382 | elseif s:buffer().commit() ==# '0' | |
1383 | let cmd = ['rm','--cached'] | |
1384 | else | |
1385 | let v:errmsg = 'fugitive: rm not supported here' | |
1386 | return 'echoerr v:errmsg' | |
1387 | endif | |
1388 | if a:force | |
1389 | let cmd += ['--force'] | |
1390 | endif | |
1391 | let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo()) | |
1392 | if v:shell_error | |
1393 | let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)') | |
1394 | return 'echoerr '.string(v:errmsg) | |
1395 | else | |
1396 | call fugitive#reload_status() | |
1397 | return 'bdelete'.(a:force ? '!' : '') | |
1398 | endif | |
1399 | endfunction | |
1400 | ||
1401 | augroup fugitive_remove | |
1402 | autocmd! | |
1403 | autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' | | |
1404 | \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" | | |
1405 | \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" | | |
1406 | \ endif | |
1407 | augroup END | |
1408 | ||
1409 | " }}}1 | |
1410 | " Gblame {{{1 | |
1411 | ||
1412 | augroup fugitive_blame | |
1413 | autocmd! | |
1414 | autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame | |
1415 | autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif | |
1416 | autocmd Syntax fugitiveblame call s:BlameSyntax() | |
1417 | autocmd User Fugitive if s:buffer().type('file', 'blob') | exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,[<f-args>])" | endif | |
1418 | augroup END | |
1419 | ||
1420 | function! s:Blame(bang,line1,line2,count,args) abort | |
1421 | try | |
1422 | if s:buffer().path() == '' | |
1423 | call s:throw('file or blob required') | |
1424 | endif | |
1425 | if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltwfs]\\|[MC]\\d*\\)\\+\\)$"') != [] | |
1426 | call s:throw('unsupported option') | |
1427 | endif | |
1428 | call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")') | |
1429 | let cmd = ['--no-pager', 'blame', '--show-number'] + a:args | |
1430 | if s:buffer().commit() =~# '\D\|..' | |
1431 | let cmd += [s:buffer().commit()] | |
1432 | else | |
1433 | let cmd += ['--contents', '-'] | |
1434 | endif | |
1435 | let basecmd = escape(call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo()),'!') | |
1436 | try | |
1437 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' | |
1438 | if !s:repo().bare() | |
1439 | let dir = getcwd() | |
1440 | execute cd.'`=s:repo().tree()`' | |
1441 | endif | |
1442 | if a:count | |
1443 | execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g') | |
1444 | else | |
1445 | let error = tempname() | |
1446 | let temp = error.'.fugitiveblame' | |
1447 | if &shell =~# 'csh' | |
1448 | silent! execute '%write !('.basecmd.' > '.temp.') >& '.error | |
1449 | else | |
1450 | silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error | |
1451 | endif | |
1452 | if exists('l:dir') | |
1453 | execute cd.'`=dir`' | |
1454 | unlet dir | |
1455 | endif | |
1456 | if v:shell_error | |
1457 | call s:throw(join(readfile(error),"\n")) | |
1458 | endif | |
1459 | let bufnr = bufnr('') | |
1460 | let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)' | |
1461 | if &l:wrap | |
1462 | let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)' | |
1463 | endif | |
1464 | if &l:foldenable | |
1465 | let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)' | |
1466 | endif | |
1467 | let winnr = winnr() | |
1468 | windo set noscrollbind | |
1469 | exe winnr.'wincmd w' | |
1470 | setlocal scrollbind nowrap nofoldenable | |
1471 | let top = line('w0') + &scrolloff | |
1472 | let current = line('.') | |
1473 | let s:temp_files[temp] = s:repo().dir() | |
1474 | exe 'leftabove vsplit '.temp | |
1475 | let b:fugitive_blamed_bufnr = bufnr | |
1476 | let w:fugitive_leave = restore | |
1477 | let b:fugitive_blame_arguments = join(a:args,' ') | |
1478 | execute top | |
1479 | normal! zt | |
1480 | execute current | |
1481 | execute "vertical resize ".(match(getline('.'),'\s\+\d\+)')+1) | |
1482 | setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame | |
1483 | if exists('+relativenumber') | |
1484 | setlocal norelativenumber | |
1485 | endif | |
1486 | nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameJump('')<CR> | |
1487 | nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR> | |
1488 | nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR> | |
1489 | nnoremap <buffer> <silent> o :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft")." split", 0, matchstr(getline('.'),'\x\+'))<CR> | |
1490 | nnoremap <buffer> <silent> O :<C-U>exe <SID>Edit("tabedit", 0, matchstr(getline('.'),'\x\+'))<CR> | |
1491 | syncbind | |
1492 | endif | |
1493 | finally | |
1494 | if exists('l:dir') | |
1495 | execute cd.'`=dir`' | |
1496 | endif | |
1497 | endtry | |
1498 | return '' | |
1499 | catch /^fugitive:/ | |
1500 | return 'echoerr v:errmsg' | |
1501 | endtry | |
1502 | endfunction | |
1503 | ||
1504 | function! s:BlameJump(suffix) abort | |
1505 | let commit = matchstr(getline('.'),'^\^\=\zs\x\+') | |
1506 | if commit =~# '^0\+$' | |
1507 | let commit = ':0' | |
1508 | endif | |
1509 | let lnum = matchstr(getline('.'),'\d\+\ze\s\+[([:digit:]]') | |
1510 | let path = matchstr(getline('.'),'^\^\=\zs\x\+\s\+\zs.\{-\}\ze\s*\d\+ ') | |
1511 | if path ==# '' | |
1512 | let path = s:buffer(b:fugitive_blamed_bufnr).path() | |
1513 | endif | |
1514 | let args = b:fugitive_blame_arguments | |
1515 | let offset = line('.') - line('w0') | |
1516 | let bufnr = bufnr('%') | |
1517 | let winnr = bufwinnr(b:fugitive_blamed_bufnr) | |
1518 | if winnr > 0 | |
1519 | exe winnr.'wincmd w' | |
1520 | endif | |
1521 | execute s:Edit('edit', 0, commit.a:suffix.':'.path) | |
1522 | if winnr > 0 | |
1523 | exe bufnr.'bdelete' | |
1524 | endif | |
1525 | execute 'Gblame '.args | |
1526 | execute lnum | |
1527 | let delta = line('.') - line('w0') - offset | |
1528 | if delta > 0 | |
1529 | execute 'norm! 'delta."\<C-E>" | |
1530 | elseif delta < 0 | |
1531 | execute 'norm! '(-delta)."\<C-Y>" | |
1532 | endif | |
1533 | syncbind | |
1534 | return '' | |
1535 | endfunction | |
1536 | ||
1537 | function! s:BlameSyntax() abort | |
1538 | let b:current_syntax = 'fugitiveblame' | |
1539 | syn match FugitiveblameBoundary "^\^" | |
1540 | syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite | |
1541 | syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite | |
1542 | syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite | |
1543 | syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline | |
1544 | syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation | |
1545 | syn match FugitiveblameLineNumber " \@<=\d\+)\@=" contained containedin=FugitiveblameAnnotation | |
1546 | syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite | |
1547 | syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite | |
1548 | syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite | |
1549 | syn match FugitiveblameShort "\d\+)" contained contains=FugitiveblameLineNumber | |
1550 | syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation | |
1551 | hi def link FugitiveblameBoundary Keyword | |
1552 | hi def link FugitiveblameHash Identifier | |
1553 | hi def link FugitiveblameUncommitted Function | |
1554 | hi def link FugitiveblameTime PreProc | |
1555 | hi def link FugitiveblameLineNumber Number | |
1556 | hi def link FugitiveblameOriginalFile String | |
1557 | hi def link FugitiveblameOriginalLineNumber Float | |
1558 | hi def link FugitiveblameShort FugitiveblameDelimiter | |
1559 | hi def link FugitiveblameDelimiter Delimiter | |
1560 | hi def link FugitiveblameNotCommittedYet Comment | |
1561 | endfunction | |
1562 | ||
1563 | " }}}1 | |
1564 | " Gbrowse {{{1 | |
1565 | ||
1566 | call s:command("-bar -bang -count=0 -nargs=? -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)") | |
1567 | ||
1568 | function! s:Browse(bang,line1,count,...) abort | |
1569 | try | |
1570 | let rev = a:0 ? substitute(a:1,'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : '' | |
1571 | if rev ==# '' | |
1572 | let expanded = s:buffer().rev() | |
1573 | elseif rev ==# ':' | |
1574 | let expanded = s:buffer().path('/') | |
1575 | else | |
1576 | let expanded = s:buffer().expand(rev) | |
1577 | endif | |
1578 | let full = s:repo().translate(expanded) | |
1579 | let commit = '' | |
1580 | if full =~# '^fugitive://' | |
1581 | let commit = matchstr(full,'://.*//\zs\w\+') | |
1582 | let path = matchstr(full,'://.*//\w\+\zs/.*') | |
1583 | if commit =~ '..' | |
1584 | let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':')) | |
1585 | else | |
1586 | let type = 'blob' | |
1587 | endif | |
1588 | let path = path[1:-1] | |
1589 | elseif s:repo().bare() | |
1590 | let path = '.git/' . full[strlen(s:repo().dir())+1:-1] | |
1591 | let type = '' | |
1592 | else | |
1593 | let path = full[strlen(s:repo().tree())+1:-1] | |
1594 | if path =~# '^\.git/' | |
1595 | let type = '' | |
1596 | elseif isdirectory(full) | |
1597 | let type = 'tree' | |
1598 | else | |
1599 | let type = 'blob' | |
1600 | endif | |
1601 | endif | |
1602 | if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1])) | |
1603 | let body = readfile(s:repo().dir(path[5:-1]))[0] | |
1604 | if body =~# '^\x\{40\}$' | |
1605 | let commit = body | |
1606 | let type = 'commit' | |
1607 | let path = '' | |
1608 | elseif body =~# '^ref: refs/' | |
1609 | let path = '.git/' . matchstr(body,'ref: \zs.*') | |
1610 | endif | |
1611 | endif | |
1612 | ||
1613 | if a:0 && a:1 =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$' | |
1614 | let remote = matchstr(a:1,'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$') | |
1615 | elseif path =~# '^\.git/refs/remotes/.' | |
1616 | let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+') | |
1617 | else | |
1618 | let remote = 'origin' | |
1619 | let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]') | |
1620 | if branch ==# '' && path =~# '^\.git/refs/\w\+/' | |
1621 | let branch = s:sub(path,'^\.git/refs/\w+/','') | |
1622 | endif | |
1623 | if filereadable(s:repo().dir('refs/remotes/'.branch)) | |
1624 | let remote = matchstr(branch,'[^/]\+') | |
1625 | let rev = rev[strlen(remote)+1:-1] | |
1626 | else | |
1627 | if branch ==# '' | |
1628 | let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*') | |
1629 | endif | |
1630 | if branch != '' | |
1631 | let remote = s:repo().git_chomp('config','branch.'.branch.'.remote') | |
1632 | if remote =~# '^\.\=$' | |
1633 | let remote = 'origin' | |
1634 | elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]' | |
1635 | let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1] | |
1636 | endif | |
1637 | endif | |
1638 | endif | |
1639 | endif | |
1640 | ||
1641 | let raw = s:repo().git_chomp('config','remote.'.remote.'.url') | |
1642 | if raw ==# '' | |
1643 | let raw = remote | |
1644 | endif | |
1645 | ||
1646 | let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count) | |
1647 | if url == '' | |
1648 | let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count ? a:line1 : 0) | |
1649 | endif | |
1650 | ||
1651 | if url == '' | |
1652 | call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote") | |
1653 | endif | |
1654 | ||
1655 | if a:bang | |
1656 | let @* = url | |
1657 | return 'echomsg '.string(url) | |
1658 | else | |
1659 | return 'echomsg '.string(url).'|call fugitive#buffer().repo().git_chomp("web--browse",'.string(url).')' | |
1660 | endif | |
1661 | catch /^fugitive:/ | |
1662 | return 'echoerr v:errmsg' | |
1663 | endtry | |
1664 | endfunction | |
1665 | ||
1666 | function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort | |
1667 | let path = a:path | |
1668 | let repo_path = matchstr(a:url,'^\%(https\=://\|git://\|git@\)github\.com[/:]\zs.\{-\}\ze\%(\.git\)\=$') | |
1669 | if repo_path ==# '' | |
1670 | return '' | |
1671 | endif | |
1672 | let root = 'https://github.com/' . repo_path | |
1673 | if path =~# '^\.git/refs/heads/' | |
1674 | let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1] | |
1675 | if branch ==# '' | |
1676 | return root . '/commits/' . path[16:-1] | |
1677 | else | |
1678 | return root . '/commits/' . branch | |
1679 | endif | |
1680 | elseif path =~# '^\.git/refs/.' | |
1681 | return root . '/commits/' . matchstr(path,'[^/]\+$') | |
1682 | elseif path =~# '.git/\%(config$\|hooks\>\)' | |
1683 | return root . '/admin' | |
1684 | elseif path =~# '^\.git\>' | |
1685 | return root | |
1686 | endif | |
1687 | if a:rev =~# '^[[:alnum:]._-]\+:' | |
1688 | let commit = matchstr(a:rev,'^[^:]*') | |
1689 | elseif a:commit =~# '^\d\=$' | |
1690 | let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*') | |
1691 | let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1] | |
1692 | if commit ==# '' | |
1693 | let commit = local | |
1694 | endif | |
1695 | else | |
1696 | let commit = a:commit | |
1697 | endif | |
1698 | if a:type == 'tree' | |
1699 | let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','') | |
1700 | elseif a:type == 'blob' | |
1701 | let url = root . '/blob/' . commit . '/' . path | |
1702 | if a:line2 && a:line1 == a:line2 | |
1703 | let url .= '#L' . a:line1 | |
1704 | elseif a:line2 | |
1705 | let url .= '#L' . a:line1 . '-' . a:line2 | |
1706 | endif | |
1707 | elseif a:type == 'tag' | |
1708 | let commit = matchstr(getline(3),'^tag \zs.*') | |
1709 | let url = root . '/tree/' . commit | |
1710 | else | |
1711 | let url = root . '/commit/' . commit | |
1712 | endif | |
1713 | return url | |
1714 | endfunction | |
1715 | ||
1716 | function! s:instaweb_url(repo,rev,commit,path,type,...) abort | |
1717 | let output = a:repo.git_chomp('instaweb','-b','unknown') | |
1718 | if output =~# 'http://' | |
1719 | let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t') | |
1720 | else | |
1721 | return '' | |
1722 | endif | |
1723 | if a:path =~# '^\.git/refs/.' | |
1724 | return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*') | |
1725 | elseif a:path =~# '^\.git\>' | |
1726 | return root | |
1727 | endif | |
1728 | let url = root | |
1729 | if a:commit =~# '^\x\{40\}$' | |
1730 | if a:type ==# 'commit' | |
1731 | let url .= ';a=commit' | |
1732 | endif | |
1733 | let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path)) | |
1734 | else | |
1735 | if a:type ==# 'blob' | |
1736 | let tmp = tempname() | |
1737 | silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp | |
1738 | let url .= ';h=' . readfile(tmp)[0] | |
1739 | else | |
1740 | try | |
1741 | let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path) | |
1742 | catch /^fugitive:/ | |
1743 | call s:throw('fugitive: cannot browse uncommitted file') | |
1744 | endtry | |
1745 | endif | |
1746 | let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$') | |
1747 | endif | |
1748 | if a:path !=# '' | |
1749 | let url .= ';f=' . a:path | |
1750 | endif | |
1751 | if a:0 && a:1 | |
1752 | let url .= '#l' . a:1 | |
1753 | endif | |
1754 | return url | |
1755 | endfunction | |
1756 | ||
1757 | " }}}1 | |
1758 | " File access {{{1 | |
1759 | ||
1760 | function! s:ReplaceCmd(cmd,...) abort | |
1761 | let fn = bufname('') | |
1762 | let tmp = tempname() | |
1763 | let prefix = '' | |
1764 | try | |
1765 | if a:0 && a:1 != '' | |
1766 | if &shell =~# 'cmd' | |
1767 | let old_index = $GIT_INDEX_FILE | |
1768 | let $GIT_INDEX_FILE = a:1 | |
1769 | else | |
1770 | let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' ' | |
1771 | endif | |
1772 | endif | |
1773 | if &shell =~# 'cmd' | |
1774 | call system('cmd /c "'.prefix.a:cmd.' > '.tmp.'"') | |
1775 | else | |
1776 | call system(' ('.prefix.a:cmd.' > '.tmp.') ') | |
1777 | endif | |
1778 | finally | |
1779 | if exists('old_index') | |
1780 | let $GIT_INDEX_FILE = old_index | |
1781 | endif | |
1782 | endtry | |
1783 | silent exe 'keepalt file '.tmp | |
1784 | silent edit! | |
1785 | silent exe 'keepalt file '.s:fnameescape(fn) | |
1786 | call delete(tmp) | |
1787 | if bufname('$') == tmp | |
1788 | silent execute 'bwipeout '.bufnr('$') | |
1789 | endif | |
1790 | silent exe 'doau BufReadPost '.s:fnameescape(fn) | |
1791 | endfunction | |
1792 | ||
1793 | function! s:BufReadIndex() | |
1794 | if !exists('b:fugitive_display_format') | |
1795 | let b:fugitive_display_format = filereadable(expand('%').'.lock') | |
1796 | endif | |
1797 | let b:fugitive_display_format = b:fugitive_display_format % 2 | |
1798 | let b:fugitive_type = 'index' | |
1799 | try | |
1800 | let b:git_dir = s:repo().dir() | |
1801 | setlocal noro ma | |
1802 | if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p') | |
1803 | let index = '' | |
1804 | else | |
1805 | let index = expand('%:p') | |
1806 | endif | |
1807 | if b:fugitive_display_format | |
1808 | call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index) | |
1809 | set ft=git nospell | |
1810 | else | |
1811 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' | |
1812 | let dir = getcwd() | |
1813 | try | |
1814 | execute cd.'`=s:repo().tree()`' | |
1815 | call s:ReplaceCmd(s:repo().git_command('status'),index) | |
1816 | finally | |
1817 | execute cd.'`=dir`' | |
1818 | endtry | |
1819 | set ft=gitcommit | |
1820 | endif | |
1821 | setlocal ro noma nomod nomodeline bufhidden=wipe | |
1822 | call s:JumpInit() | |
1823 | nunmap <buffer> P | |
1824 | nunmap <buffer> ~ | |
1825 | nnoremap <buffer> <silent> <C-N> :call search('^#\t.*','W')<Bar>.<CR> | |
1826 | nnoremap <buffer> <silent> <C-P> :call search('^#\t.*','Wbe')<Bar>.<CR> | |
1827 | nnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR> | |
1828 | xnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line("'<"),line("'>"))<CR> | |
1829 | nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR> | |
1830 | nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR> | |
1831 | nnoremap <buffer> <silent> C :<C-U>Gcommit<CR> | |
1832 | nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR> | |
1833 | nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR> | |
1834 | nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR> | |
1835 | nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gvdiff')<CR> | |
1836 | nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gvdiff')<CR> | |
1837 | nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR> | |
1838 | nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR> | |
1839 | nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR> | |
1840 | nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR> | |
1841 | nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR> | |
1842 | xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR> | |
1843 | nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR> | |
1844 | nnoremap <buffer> <silent> R :<C-U>edit<CR> | |
1845 | catch /^fugitive:/ | |
1846 | return 'echoerr v:errmsg' | |
1847 | endtry | |
1848 | endfunction | |
1849 | ||
1850 | function! s:FileRead() | |
1851 | try | |
1852 | let repo = s:repo(s:ExtractGitDir(expand('<amatch>'))) | |
1853 | let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&') | |
1854 | let hash = repo.rev_parse(path) | |
1855 | if path =~ '^:' | |
1856 | let type = 'blob' | |
1857 | else | |
1858 | let type = repo.git_chomp('cat-file','-t',hash) | |
1859 | endif | |
1860 | " TODO: use count, if possible | |
1861 | return "read !".escape(repo.git_command('cat-file',type,hash),'%#\') | |
1862 | catch /^fugitive:/ | |
1863 | return 'echoerr v:errmsg' | |
1864 | endtry | |
1865 | endfunction | |
1866 | ||
1867 | function! s:BufReadIndexFile() | |
1868 | try | |
1869 | let b:fugitive_type = 'blob' | |
1870 | let b:git_dir = s:repo().dir() | |
1871 | call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1())) | |
1872 | if &bufhidden ==# '' | |
1873 | setlocal bufhidden=delete | |
1874 | endif | |
1875 | return '' | |
1876 | catch /^fugitive: rev-parse/ | |
1877 | silent exe 'doau BufNewFile '.s:fnameescape(bufname('')) | |
1878 | return '' | |
1879 | catch /^fugitive:/ | |
1880 | return 'echoerr v:errmsg' | |
1881 | endtry | |
1882 | endfunction | |
1883 | ||
1884 | function! s:BufWriteIndexFile() | |
1885 | let tmp = tempname() | |
1886 | try | |
1887 | let path = matchstr(expand('<amatch>'),'//\d/\zs.*') | |
1888 | let stage = matchstr(expand('<amatch>'),'//\zs\d') | |
1889 | silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp | |
1890 | let sha1 = readfile(tmp)[0] | |
1891 | let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+') | |
1892 | if old_mode == '' | |
1893 | let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644' | |
1894 | endif | |
1895 | let info = old_mode.' '.sha1.' '.stage."\t".path | |
1896 | call writefile([info],tmp) | |
1897 | if has('win32') | |
1898 | let error = system('type '.tmp.'|'.s:repo().git_command('update-index','--index-info')) | |
1899 | else | |
1900 | let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp) | |
1901 | endif | |
1902 | if v:shell_error == 0 | |
1903 | setlocal nomodified | |
1904 | silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p')) | |
1905 | call fugitive#reload_status() | |
1906 | return '' | |
1907 | else | |
1908 | return 'echoerr '.string('fugitive: '.error) | |
1909 | endif | |
1910 | finally | |
1911 | call delete(tmp) | |
1912 | endtry | |
1913 | endfunction | |
1914 | ||
1915 | function! s:BufReadObject() | |
1916 | try | |
1917 | setlocal noro ma | |
1918 | let b:git_dir = s:repo().dir() | |
1919 | let hash = s:buffer().sha1() | |
1920 | if !exists("b:fugitive_type") | |
1921 | let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash) | |
1922 | endif | |
1923 | if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$' | |
1924 | return "echoerr 'fugitive: unrecognized git type'" | |
1925 | endif | |
1926 | let firstline = getline('.') | |
1927 | if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob' | |
1928 | let b:fugitive_display_format = +getbufvar('#','fugitive_display_format') | |
1929 | endif | |
1930 | ||
1931 | let pos = getpos('.') | |
1932 | silent %delete | |
1933 | setlocal endofline | |
1934 | ||
1935 | if b:fugitive_type == 'tree' | |
1936 | let b:fugitive_display_format = b:fugitive_display_format % 2 | |
1937 | if b:fugitive_display_format | |
1938 | call s:ReplaceCmd(s:repo().git_command('ls-tree',hash)) | |
1939 | else | |
1940 | call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash)) | |
1941 | endif | |
1942 | elseif b:fugitive_type == 'tag' | |
1943 | let b:fugitive_display_format = b:fugitive_display_format % 2 | |
1944 | if b:fugitive_display_format | |
1945 | call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash)) | |
1946 | else | |
1947 | call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash)) | |
1948 | endif | |
1949 | elseif b:fugitive_type == 'commit' | |
1950 | let b:fugitive_display_format = b:fugitive_display_format % 2 | |
1951 | if b:fugitive_display_format | |
1952 | call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash)) | |
1953 | else | |
1954 | call s:ReplaceCmd(s:repo().git_command('show','--no-color','--pretty=format:tree %T%nparent %P%nauthor %an <%ae> %ad%ncommitter %cn <%ce> %cd%nencoding %e%n%n%s%n%n%b',hash)) | |
1955 | call search('^parent ') | |
1956 | if getline('.') ==# 'parent ' | |
1957 | silent delete_ | |
1958 | else | |
1959 | silent s/\%(^parent\)\@<! /\rparent /ge | |
1960 | endif | |
1961 | if search('^encoding \%(<unknown>\)\=$','W',line('.')+3) | |
1962 | silent delete_ | |
1963 | end | |
1964 | 1 | |
1965 | endif | |
1966 | elseif b:fugitive_type ==# 'blob' | |
1967 | call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash)) | |
1968 | endif | |
1969 | call setpos('.',pos) | |
1970 | setlocal ro noma nomod nomodeline | |
1971 | if &bufhidden ==# '' | |
1972 | setlocal bufhidden=delete | |
1973 | endif | |
1974 | if b:fugitive_type !=# 'blob' | |
1975 | set filetype=git | |
1976 | nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR> | |
1977 | nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR> | |
1978 | else | |
1979 | call s:JumpInit() | |
1980 | endif | |
1981 | ||
1982 | return '' | |
1983 | catch /^fugitive:/ | |
1984 | return 'echoerr v:errmsg' | |
1985 | endtry | |
1986 | endfunction | |
1987 | ||
1988 | augroup fugitive_files | |
1989 | autocmd! | |
1990 | autocmd BufReadCmd *.git/index exe s:BufReadIndex() | |
1991 | autocmd BufReadCmd *.git/*index*.lock exe s:BufReadIndex() | |
1992 | autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead() | |
1993 | autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile() | |
1994 | autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile() | |
1995 | autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject() | |
1996 | autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead() | |
1997 | autocmd FileType git call s:JumpInit() | |
1998 | augroup END | |
1999 | ||
2000 | " }}}1 | |
2001 | " Temp files {{{1 | |
2002 | ||
2003 | let s:temp_files = {} | |
2004 | ||
2005 | augroup fugitive_temp | |
2006 | autocmd! | |
2007 | autocmd BufNewFile,BufReadPost * | |
2008 | \ if has_key(s:temp_files,expand('<amatch>:p')) | | |
2009 | \ let b:git_dir = s:temp_files[expand('<amatch>:p')] | | |
2010 | \ let b:git_type = 'temp' | | |
2011 | \ call s:Detect(expand('<amatch>:p')) | | |
2012 | \ setlocal bufhidden=delete | | |
2013 | \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR> | | |
2014 | \ endif | |
2015 | augroup END | |
2016 | ||
2017 | " }}}1 | |
2018 | " Go to file {{{1 | |
2019 | ||
2020 | function! s:JumpInit() abort | |
2021 | nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR> | |
2022 | if !&modifiable | |
2023 | nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR> | |
2024 | nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR> | |
2025 | nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR> | |
2026 | nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR> | |
2027 | nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR> | |
2028 | nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR> | |
2029 | nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR> | |
2030 | nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR> | |
2031 | nnoremap <buffer> <silent> cp :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR> | |
2032 | endif | |
2033 | endfunction | |
2034 | ||
2035 | function! s:GF(mode) abort | |
2036 | try | |
2037 | let buffer = s:buffer() | |
2038 | let myhash = buffer.sha1() | |
2039 | if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w' | |
2040 | let myhash = matchstr(getline(1),'^\w\+ \zs\S\+') | |
2041 | endif | |
2042 | ||
2043 | if buffer.type('tree') | |
2044 | let showtree = (getline(1) =~# '^tree ' && getline(2) == "") | |
2045 | if showtree && line('.') == 1 | |
2046 | return "" | |
2047 | elseif showtree && line('.') > 2 | |
2048 | return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$','')) | |
2049 | elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t' | |
2050 | return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')) | |
2051 | endif | |
2052 | ||
2053 | elseif buffer.type('blob') | |
2054 | let ref = expand("<cfile>") | |
2055 | try | |
2056 | let sha1 = buffer.repo().rev_parse(ref) | |
2057 | catch /^fugitive:/ | |
2058 | endtry | |
2059 | if exists('sha1') | |
2060 | return s:Edit(a:mode,0,ref) | |
2061 | endif | |
2062 | ||
2063 | else | |
2064 | ||
2065 | " Index | |
2066 | if getline('.') =~# '^\d\{6\} \x\{40\} \d\t' | |
2067 | let ref = matchstr(getline('.'),'\x\{40\}') | |
2068 | let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':') | |
2069 | return s:Edit(a:mode,0,file) | |
2070 | ||
2071 | elseif getline('.') =~# '^#\trenamed:.* -> ' | |
2072 | let file = '/'.matchstr(getline('.'),' -> \zs.*') | |
2073 | return s:Edit(a:mode,0,file) | |
2074 | elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.' | |
2075 | let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( (new commits)\)\=$') | |
2076 | return s:Edit(a:mode,0,file) | |
2077 | elseif getline('.') =~# '^#\t.' | |
2078 | let file = '/'.matchstr(getline('.'),'#\t\zs.*') | |
2079 | return s:Edit(a:mode,0,file) | |
2080 | elseif getline('.') =~# ': needs merge$' | |
2081 | let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$') | |
2082 | return s:Edit(a:mode,0,file).'|Gdiff' | |
2083 | ||
2084 | elseif getline('.') ==# '# Not currently on any branch.' | |
2085 | return s:Edit(a:mode,0,'HEAD') | |
2086 | elseif getline('.') =~# '^# On branch ' | |
2087 | let file = 'refs/heads/'.getline('.')[12:] | |
2088 | return s:Edit(a:mode,0,file) | |
2089 | elseif getline('.') =~# "^# Your branch .*'" | |
2090 | let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'") | |
2091 | return s:Edit(a:mode,0,file) | |
2092 | endif | |
2093 | ||
2094 | let showtree = (getline(1) =~# '^tree ' && getline(2) == "") | |
2095 | ||
2096 | if getline('.') =~# '^ref: ' | |
2097 | let ref = strpart(getline('.'),5) | |
2098 | ||
2099 | elseif getline('.') =~# '^commit \x\{40\}\>' | |
2100 | let ref = matchstr(getline('.'),'\x\{40\}') | |
2101 | return s:Edit(a:mode,0,ref) | |
2102 | ||
2103 | elseif getline('.') =~# '^parent \x\{40\}\>' | |
2104 | let ref = matchstr(getline('.'),'\x\{40\}') | |
2105 | let line = line('.') | |
2106 | let parent = 0 | |
2107 | while getline(line) =~# '^parent ' | |
2108 | let parent += 1 | |
2109 | let line -= 1 | |
2110 | endwhile | |
2111 | return s:Edit(a:mode,0,ref) | |
2112 | ||
2113 | elseif getline('.') =~ '^tree \x\{40\}$' | |
2114 | let ref = matchstr(getline('.'),'\x\{40\}') | |
2115 | if s:repo().rev_parse(myhash.':') == ref | |
2116 | let ref = myhash.':' | |
2117 | endif | |
2118 | return s:Edit(a:mode,0,ref) | |
2119 | ||
2120 | elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$' | |
2121 | let ref = matchstr(getline('.'),'\x\{40\}') | |
2122 | let type = matchstr(getline(line('.')+1),'type \zs.*') | |
2123 | ||
2124 | elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$' | |
2125 | return '' | |
2126 | ||
2127 | elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>' | |
2128 | let ref = matchstr(getline('.'),'\x\{40\}') | |
2129 | echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*') | |
2130 | ||
2131 | elseif getline('.') =~# '^[+-]\{3\} [ab/]' | |
2132 | let ref = getline('.')[4:] | |
2133 | ||
2134 | elseif getline('.') =~# '^rename from ' | |
2135 | let ref = 'a/'.getline('.')[12:] | |
2136 | elseif getline('.') =~# '^rename to ' | |
2137 | let ref = 'b/'.getline('.')[10:] | |
2138 | ||
2139 | elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)' | |
2140 | let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)') | |
2141 | let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)') | |
2142 | let dcmd = 'Gdiff' | |
2143 | ||
2144 | elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)' | |
2145 | let line = getline(line('.')-1) | |
2146 | let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)') | |
2147 | let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)') | |
2148 | let dcmd = 'Gdiff!' | |
2149 | ||
2150 | elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$' | |
2151 | let ref = getline('.') | |
2152 | else | |
2153 | let ref = '' | |
2154 | endif | |
2155 | ||
2156 | if myhash ==# '' | |
2157 | let ref = s:sub(ref,'^a/','HEAD:') | |
2158 | let ref = s:sub(ref,'^b/',':0:') | |
2159 | if exists('dref') | |
2160 | let dref = s:sub(dref,'^a/','HEAD:') | |
2161 | endif | |
2162 | else | |
2163 | let ref = s:sub(ref,'^a/',myhash.'^:') | |
2164 | let ref = s:sub(ref,'^b/',myhash.':') | |
2165 | if exists('dref') | |
2166 | let dref = s:sub(dref,'^a/',myhash.'^:') | |
2167 | endif | |
2168 | endif | |
2169 | ||
2170 | if ref ==# '/dev/null' | |
2171 | " Empty blob | |
2172 | let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391' | |
2173 | endif | |
2174 | ||
2175 | if exists('dref') | |
2176 | return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref) | |
2177 | elseif ref != "" | |
2178 | return s:Edit(a:mode,0,ref) | |
2179 | endif | |
2180 | ||
2181 | endif | |
2182 | return '' | |
2183 | catch /^fugitive:/ | |
2184 | return 'echoerr v:errmsg' | |
2185 | endtry | |
2186 | endfunction | |
2187 | ||
2188 | " }}}1 | |
2189 | " Statusline {{{1 | |
2190 | ||
2191 | function! s:repo_head_ref() dict abort | |
2192 | return readfile(s:repo().dir('HEAD'))[0] | |
2193 | endfunction | |
2194 | ||
2195 | call s:add_methods('repo',['head_ref']) | |
2196 | ||
2197 | function! fugitive#statusline(...) | |
2198 | if !exists('b:git_dir') | |
2199 | return '' | |
2200 | endif | |
2201 | let status = '' | |
2202 | if s:buffer().commit() != '' | |
2203 | let status .= ':' . s:buffer().commit()[0:7] | |
2204 | endif | |
2205 | let head = s:repo().head_ref() | |
2206 | if head =~# '^ref: ' | |
2207 | let status .= s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','(').')' | |
2208 | elseif head =~# '^\x\{40\}$' | |
2209 | let status .= '('.head[0:7].')' | |
2210 | endif | |
2211 | if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]' | |
2212 | return ',GIT'.status | |
2213 | else | |
2214 | return '[Git'.status.']' | |
2215 | endif | |
2216 | endfunction | |
2217 | ||
2218 | " }}}1 | |
2219 | ||
2220 | " vim:set ft=vim ts=8 sw=2 sts=2: |