fugitive.vim 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778
  1. " fugitive.vim - A Git wrapper so awesome, it should be illegal
  2. " Maintainer: Tim Pope <vimNOSPAM@tpope.org>
  3. " Version: 1.1
  4. " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim
  5. if exists('g:loaded_fugitive') || &cp
  6. finish
  7. endif
  8. let g:loaded_fugitive = 1
  9. if !exists('g:fugitive_git_executable')
  10. let g:fugitive_git_executable = 'git'
  11. endif
  12. " Utility {{{1
  13. function! s:function(name) abort
  14. return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
  15. endfunction
  16. function! s:sub(str,pat,rep) abort
  17. return substitute(a:str,'\v\C'.a:pat,a:rep,'')
  18. endfunction
  19. function! s:gsub(str,pat,rep) abort
  20. return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
  21. endfunction
  22. function! s:shellesc(arg) abort
  23. if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
  24. return a:arg
  25. elseif &shell =~# 'cmd' && a:arg !~# '"'
  26. return '"'.a:arg.'"'
  27. else
  28. return shellescape(a:arg)
  29. endif
  30. endfunction
  31. function! s:fnameescape(file) abort
  32. if exists('*fnameescape')
  33. return fnameescape(a:file)
  34. else
  35. return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
  36. endif
  37. endfunction
  38. function! s:throw(string) abort
  39. let v:errmsg = 'fugitive: '.a:string
  40. throw v:errmsg
  41. endfunction
  42. function! s:warn(str)
  43. echohl WarningMsg
  44. echomsg a:str
  45. echohl None
  46. let v:warningmsg = a:str
  47. endfunction
  48. function! s:shellslash(path)
  49. if exists('+shellslash') && !&shellslash
  50. return s:gsub(a:path,'\\','/')
  51. else
  52. return a:path
  53. endif
  54. endfunction
  55. function! s:add_methods(namespace, method_names) abort
  56. for name in a:method_names
  57. let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
  58. endfor
  59. endfunction
  60. let s:commands = []
  61. function! s:command(definition) abort
  62. let s:commands += [a:definition]
  63. endfunction
  64. function! s:define_commands()
  65. for command in s:commands
  66. exe 'command! -buffer '.command
  67. endfor
  68. endfunction
  69. function! s:compatibility_check()
  70. if exists('b:git_dir') && exists('*GitBranchInfoCheckGitDir') && !exists('g:fugitive_did_compatibility_warning')
  71. let g:fugitive_did_compatibility_warning = 1
  72. call s:warn("See http://github.com/tpope/vim-fugitive/issues#issue/1 for why you should remove git-branch-info.vim")
  73. endif
  74. endfunction
  75. augroup fugitive_utility
  76. autocmd!
  77. autocmd User Fugitive call s:define_commands()
  78. autocmd VimEnter * call s:compatibility_check()
  79. augroup END
  80. let s:abstract_prototype = {}
  81. " }}}1
  82. " Initialization {{{1
  83. function! s:ExtractGitDir(path) abort
  84. let path = s:shellslash(a:path)
  85. if path =~? '^fugitive://.*//'
  86. return matchstr(path,'fugitive://\zs.\{-\}\ze//')
  87. endif
  88. let fn = fnamemodify(path,':s?[\/]$??')
  89. let ofn = ""
  90. let nfn = fn
  91. while fn != ofn
  92. if isdirectory(fn . '/.git')
  93. return s:sub(simplify(fnamemodify(fn . '/.git',':p')),'\W$','')
  94. elseif fn =~ '\.git$' && filereadable(fn . '/HEAD')
  95. return s:sub(simplify(fnamemodify(fn,':p')),'\W$','')
  96. endif
  97. let ofn = fn
  98. let fn = fnamemodify(ofn,':h')
  99. endwhile
  100. return ''
  101. endfunction
  102. function! s:Detect(path)
  103. if exists('b:git_dir') && b:git_dir ==# ''
  104. unlet b:git_dir
  105. endif
  106. if !exists('b:git_dir')
  107. let dir = s:ExtractGitDir(a:path)
  108. if dir != ''
  109. let b:git_dir = dir
  110. endif
  111. endif
  112. if exists('b:git_dir')
  113. silent doautocmd User Fugitive
  114. cnoremap <expr> <buffer> <C-R><C-G> fugitive#buffer().rev()
  115. let buffer = fugitive#buffer()
  116. if expand('%:p') =~# '//'
  117. call buffer.setvar('&path',s:sub(buffer.getvar('&path'),'^\.%(,|$)',''))
  118. endif
  119. if b:git_dir !~# ',' && stridx(buffer.getvar('&tags'),b:git_dir.'/tags') == -1
  120. call buffer.setvar('&tags',buffer.getvar('&tags').','.b:git_dir.'/tags')
  121. endif
  122. endif
  123. endfunction
  124. augroup fugitive
  125. autocmd!
  126. autocmd BufNewFile,BufReadPost * call s:Detect(expand('<amatch>:p'))
  127. autocmd FileType netrw call s:Detect(expand('<afile>:p'))
  128. autocmd VimEnter * if expand('<amatch>')==''|call s:Detect(getcwd())|endif
  129. autocmd BufWinLeave * execute getwinvar(+winnr(), 'fugitive_restore')
  130. augroup END
  131. " }}}1
  132. " Repository {{{1
  133. let s:repo_prototype = {}
  134. let s:repos = {}
  135. function! s:repo(...) abort
  136. let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : s:ExtractGitDir(expand('%:p')))
  137. if dir !=# ''
  138. if has_key(s:repos,dir)
  139. let repo = get(s:repos,dir)
  140. else
  141. let repo = {'git_dir': dir}
  142. let s:repos[dir] = repo
  143. endif
  144. return extend(extend(repo,s:repo_prototype,'keep'),s:abstract_prototype,'keep')
  145. endif
  146. call s:throw('not a git repository: '.expand('%:p'))
  147. endfunction
  148. function! s:repo_dir(...) dict abort
  149. return join([self.git_dir]+a:000,'/')
  150. endfunction
  151. function! s:repo_tree(...) dict abort
  152. if !self.bare()
  153. let dir = fnamemodify(self.git_dir,':h')
  154. return join([dir]+a:000,'/')
  155. endif
  156. call s:throw('no work tree')
  157. endfunction
  158. function! s:repo_bare() dict abort
  159. return self.dir() !~# '/\.git$'
  160. endfunction
  161. function! s:repo_translate(spec) dict abort
  162. if a:spec ==# '.' || a:spec ==# '/.'
  163. return self.bare() ? self.dir() : self.tree()
  164. elseif a:spec =~# '^/'
  165. return fnamemodify(self.dir(),':h').a:spec
  166. elseif a:spec =~# '^:[0-3]:'
  167. return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
  168. elseif a:spec ==# ':'
  169. if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(s:repo().dir())] ==# s:repo().dir('') && filereadable($GIT_INDEX_FILE)
  170. return fnamemodify($GIT_INDEX_FILE,':p')
  171. else
  172. return self.dir('index')
  173. endif
  174. elseif a:spec =~# '^:/'
  175. let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
  176. return 'fugitive://'.self.dir().'//'.ref
  177. elseif a:spec =~# '^:'
  178. return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
  179. elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
  180. return self.dir(a:spec)
  181. elseif filereadable(s:repo().dir('refs/'.a:spec))
  182. return self.dir('refs/'.a:spec)
  183. elseif filereadable(s:repo().dir('refs/tags/'.a:spec))
  184. return self.dir('refs/tags/'.a:spec)
  185. elseif filereadable(s:repo().dir('refs/heads/'.a:spec))
  186. return self.dir('refs/heads/'.a:spec)
  187. elseif filereadable(s:repo().dir('refs/remotes/'.a:spec))
  188. return self.dir('refs/remotes/'.a:spec)
  189. elseif filereadable(s:repo().dir('refs/remotes/'.a:spec.'/HEAD'))
  190. return self.dir('refs/remotes/'.a:spec,'/HEAD')
  191. else
  192. try
  193. let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
  194. let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
  195. return 'fugitive://'.self.dir().'//'.ref.path
  196. catch /^fugitive:/
  197. return self.tree(a:spec)
  198. endtry
  199. endif
  200. endfunction
  201. call s:add_methods('repo',['dir','tree','bare','translate'])
  202. function! s:repo_git_command(...) dict abort
  203. let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
  204. return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
  205. endfunction
  206. function! s:repo_git_chomp(...) dict abort
  207. return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
  208. endfunction
  209. function! s:repo_git_chomp_in_tree(...) dict abort
  210. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  211. let dir = getcwd()
  212. try
  213. execute cd.'`=s:repo().tree()`'
  214. return call(s:repo().git_chomp, a:000, s:repo())
  215. finally
  216. execute cd.'`=dir`'
  217. endtry
  218. endfunction
  219. function! s:repo_rev_parse(rev) dict abort
  220. let hash = self.git_chomp('rev-parse','--verify',a:rev)
  221. if hash =~ '^\x\{40\}$'
  222. return hash
  223. endif
  224. call s:throw('rev-parse '.a:rev.': '.hash)
  225. endfunction
  226. call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
  227. function! s:repo_dirglob(base) dict abort
  228. let base = s:sub(a:base,'^/','')
  229. let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
  230. call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
  231. return matches
  232. endfunction
  233. function! s:repo_superglob(base) dict abort
  234. if a:base =~# '^/' || a:base !~# ':'
  235. let results = []
  236. if a:base !~# '^/'
  237. let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
  238. let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
  239. call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
  240. let results += heads
  241. endif
  242. if !self.bare()
  243. let base = s:sub(a:base,'^/','')
  244. let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
  245. call map(matches,'s:shellslash(v:val)')
  246. call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
  247. call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
  248. let results += matches
  249. endif
  250. return results
  251. elseif a:base =~# '^:'
  252. let entries = split(self.git_chomp('ls-files','--stage'),"\n")
  253. call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
  254. if a:base !~# '^:[0-3]\%(:\|$\)'
  255. call filter(entries,'v:val[1] == "0"')
  256. call map(entries,'v:val[2:-1]')
  257. endif
  258. call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
  259. return entries
  260. else
  261. let tree = matchstr(a:base,'.*[:/]')
  262. let entries = split(self.git_chomp('ls-tree',tree),"\n")
  263. call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
  264. call map(entries,'tree.s:sub(v:val,".*\t","")')
  265. return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
  266. endif
  267. endfunction
  268. call s:add_methods('repo',['dirglob','superglob'])
  269. function! s:repo_keywordprg() dict abort
  270. let args = ' --git-dir='.escape(self.dir(),"\\\"' ").' show'
  271. if has('gui_running') && !has('win32')
  272. return g:fugitive_git_executable . ' --no-pager' . args
  273. else
  274. return g:fugitive_git_executable . args
  275. endif
  276. endfunction
  277. call s:add_methods('repo',['keywordprg'])
  278. " }}}1
  279. " Buffer {{{1
  280. let s:buffer_prototype = {}
  281. function! s:buffer(...) abort
  282. let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
  283. call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
  284. if buffer.getvar('git_dir') !=# ''
  285. return buffer
  286. endif
  287. call s:throw('not a git repository: '.expand('%:p'))
  288. endfunction
  289. function! fugitive#buffer(...) abort
  290. return s:buffer(a:0 ? a:1 : '%')
  291. endfunction
  292. function! s:buffer_getvar(var) dict abort
  293. return getbufvar(self['#'],a:var)
  294. endfunction
  295. function! s:buffer_setvar(var,value) dict abort
  296. return setbufvar(self['#'],a:var,a:value)
  297. endfunction
  298. function! s:buffer_getline(lnum) dict abort
  299. return getbufline(self['#'],a:lnum)[0]
  300. endfunction
  301. function! s:buffer_repo() dict abort
  302. return s:repo(self.getvar('git_dir'))
  303. endfunction
  304. function! s:buffer_type(...) dict abort
  305. if self.getvar('fugitive_type') != ''
  306. let type = self.getvar('fugitive_type')
  307. elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
  308. let type = 'head'
  309. elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
  310. let type = 'tree'
  311. elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
  312. let type = 'tree'
  313. elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
  314. let type = 'index'
  315. elseif isdirectory(self.spec())
  316. let type = 'directory'
  317. elseif self.spec() == ''
  318. let type = 'null'
  319. elseif filereadable(self.spec())
  320. let type = 'file'
  321. else
  322. let type = ''
  323. endif
  324. if a:0
  325. return !empty(filter(copy(a:000),'v:val ==# type'))
  326. else
  327. return type
  328. endif
  329. endfunction
  330. function! s:buffer_spec() dict abort
  331. let bufname = bufname(self['#'])
  332. return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
  333. endfunction
  334. function! s:buffer_name() dict abort
  335. return self.spec()
  336. endfunction
  337. function! s:buffer_commit() dict abort
  338. return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
  339. endfunction
  340. function! s:buffer_path(...) dict abort
  341. let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
  342. if rev != ''
  343. let rev = s:sub(rev,'\w*','')
  344. else
  345. let rev = self.spec()[strlen(self.repo().tree()) : -1]
  346. endif
  347. return s:sub(rev,'^/',a:0 ? a:1 : '')
  348. endfunction
  349. function! s:buffer_rev() dict abort
  350. let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
  351. if rev =~ '^\x/'
  352. return ':'.rev[0].':'.rev[2:-1]
  353. elseif rev =~ '.'
  354. return s:sub(rev,'/',':')
  355. elseif self.spec() =~ '\.git/index$'
  356. return ':'
  357. elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
  358. return self.spec()[strlen(self.repo().dir())+1 : -1]
  359. else
  360. return self.path()
  361. endif
  362. endfunction
  363. function! s:buffer_sha1() dict abort
  364. if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
  365. return self.repo().rev_parse(self.rev())
  366. else
  367. return ''
  368. endif
  369. endfunction
  370. function! s:buffer_expand(rev) dict abort
  371. if a:rev =~# '^:[0-3]$'
  372. let file = a:rev.self.path(':')
  373. elseif a:rev =~# '^-'
  374. let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
  375. elseif a:rev =~# '^@{'
  376. let file = 'HEAD'.a:rev.self.path(':')
  377. elseif a:rev =~# '^[~^]'
  378. let commit = s:sub(self.commit(),'^\d=$','HEAD')
  379. let file = commit.a:rev.self.path(':')
  380. else
  381. let file = a:rev
  382. endif
  383. return s:sub(file,'\%$',self.path())
  384. endfunction
  385. function! s:buffer_containing_commit() dict abort
  386. if self.commit() =~# '^\d$'
  387. return ':'
  388. elseif self.commit() =~# '.'
  389. return self.commit()
  390. else
  391. return 'HEAD'
  392. endif
  393. endfunction
  394. call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit'])
  395. " }}}1
  396. " Git {{{1
  397. call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
  398. function! s:ExecuteInTree(cmd) abort
  399. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  400. let dir = getcwd()
  401. try
  402. execute cd.'`=s:repo().tree()`'
  403. execute a:cmd
  404. finally
  405. execute cd.'`=dir`'
  406. endtry
  407. endfunction
  408. function! s:Git(bang,cmd) abort
  409. let git = s:repo().git_command()
  410. if has('gui_running') && !has('win32')
  411. let git .= ' --no-pager'
  412. endif
  413. let cmd = matchstr(a:cmd,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
  414. call s:ExecuteInTree('!'.git.' '.cmd)
  415. call fugitive#reload_status()
  416. return matchstr(a:cmd,'\v\C\\@<!%(\\\\)*\|\zs.*')
  417. endfunction
  418. function! s:GitComplete(A,L,P) abort
  419. if !exists('s:exec_path')
  420. let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
  421. endif
  422. let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
  423. if a:L =~ ' [[:alnum:]-]\+ '
  424. return s:repo().superglob(a:A)
  425. elseif a:A == ''
  426. return cmds
  427. else
  428. return filter(cmds,'v:val[0 : strlen(a:A)-1] ==# a:A')
  429. endif
  430. endfunction
  431. " }}}1
  432. " Gcd, Glcd {{{1
  433. function! s:DirComplete(A,L,P) abort
  434. let matches = s:repo().dirglob(a:A)
  435. return matches
  436. endfunction
  437. 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>)`")
  438. 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>)`")
  439. " }}}1
  440. " Gstatus {{{1
  441. call s:command("-bar Gstatus :execute s:Status()")
  442. function! s:Status() abort
  443. try
  444. Gpedit :
  445. wincmd P
  446. nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
  447. catch /^fugitive:/
  448. return 'echoerr v:errmsg'
  449. endtry
  450. return ''
  451. endfunction
  452. function! fugitive#reload_status() abort
  453. let mytab = tabpagenr()
  454. for tab in [mytab] + range(1,tabpagenr('$'))
  455. for winnr in range(1,tabpagewinnr(tab,'$'))
  456. if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
  457. execute 'tabnext '.tab
  458. if winnr != winnr()
  459. execute winnr.'wincmd w'
  460. let restorewinnr = 1
  461. endif
  462. try
  463. if !&modified
  464. call s:BufReadIndex()
  465. endif
  466. finally
  467. if exists('restorewinnr')
  468. wincmd p
  469. endif
  470. execute 'tabnext '.mytab
  471. endtry
  472. endif
  473. endfor
  474. endfor
  475. endfunction
  476. function! s:StageDiff(bang) abort
  477. let section = getline(search('^# .*:$','bnW'))
  478. let line = getline('.')
  479. let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
  480. if filename ==# '' && section == '# Changes to be committed:'
  481. return 'Git diff --cached'
  482. elseif filename ==# ''
  483. return 'Git diff'
  484. elseif line =~# '^#\trenamed:' && filename =~ ' -> '
  485. let [old, new] = split(filename,' -> ')
  486. execute 'Gedit '.s:fnameescape(':0:'.new)
  487. return 'Gdiff'.a:bang.' HEAD:'.s:fnameescape(old)
  488. elseif section == '# Changes to be committed:'
  489. execute 'Gedit '.s:fnameescape(':0:'.filename)
  490. return 'Gdiff'.a:bang.' -'
  491. else
  492. execute 'Gedit '.s:fnameescape('/'.filename)
  493. return 'Gdiff'.a:bang
  494. endif
  495. endfunction
  496. function! s:StageToggle(lnum1,lnum2) abort
  497. try
  498. let output = ''
  499. for lnum in range(a:lnum1,a:lnum2)
  500. let line = getline(lnum)
  501. if getline('.') == '# Changes to be committed:'
  502. return 'Gcommit'
  503. endif
  504. let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
  505. if filename ==# ''
  506. continue
  507. endif
  508. if !exists('first_filename')
  509. let first_filename = filename
  510. endif
  511. execute lnum
  512. let section = getline(search('^# .*:$','bnW'))
  513. if line =~# '^#\trenamed:' && filename =~ ' -> '
  514. let cmd = ['mv','--'] + reverse(split(filename,' -> '))
  515. let filename = cmd[-1]
  516. elseif section =~? ' to be '
  517. let cmd = ['reset','-q','--',filename]
  518. elseif line =~# '^#\tdeleted:'
  519. let cmd = ['rm','--',filename]
  520. else
  521. let cmd = ['add','--',filename]
  522. endif
  523. let output .= call(s:repo().git_chomp_in_tree,cmd,s:repo())."\n"
  524. endfor
  525. if exists('first_filename')
  526. let jump = first_filename
  527. let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
  528. if f !=# '' | let jump = f | endif
  529. let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
  530. if f !=# '' | let jump = f | endif
  531. silent! edit!
  532. 1
  533. redraw
  534. call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.jump.'\$','W')
  535. endif
  536. echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
  537. catch /^fugitive:/
  538. return 'echoerr v:errmsg'
  539. endtry
  540. return 'checktime'
  541. endfunction
  542. function! s:StagePatch(lnum1,lnum2) abort
  543. let add = []
  544. let reset = []
  545. for lnum in range(a:lnum1,a:lnum2)
  546. let line = getline(lnum)
  547. if line == '# Changes to be committed:'
  548. return 'Git reset --patch'
  549. elseif line == '# Changed but not updated:'
  550. return 'Git add --patch'
  551. endif
  552. let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
  553. if filename ==# ''
  554. continue
  555. endif
  556. if !exists('first_filename')
  557. let first_filename = filename
  558. endif
  559. execute lnum
  560. let section = getline(search('^# .*:$','bnW'))
  561. if line =~# '^#\trenamed:' && filename =~ ' -> '
  562. let reset += [split(filename,' -> ')[1]]
  563. elseif section =~? ' to be '
  564. let reset += [filename]
  565. elseif line !~# '^#\tdeleted:'
  566. let add += [filename]
  567. endif
  568. endfor
  569. try
  570. if !empty(add)
  571. execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
  572. endif
  573. if !empty(reset)
  574. execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
  575. endif
  576. if exists('first_filename')
  577. silent! edit!
  578. 1
  579. redraw
  580. call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\$','W')
  581. endif
  582. catch /^fugitive:/
  583. return 'echoerr v:errmsg'
  584. endtry
  585. return 'checktime'
  586. endfunction
  587. " }}}1
  588. " Gcommit {{{1
  589. call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
  590. function! s:Commit(args) abort
  591. let old_type = s:buffer().type()
  592. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  593. let dir = getcwd()
  594. let msgfile = s:repo().dir('COMMIT_EDITMSG')
  595. let outfile = tempname()
  596. let errorfile = tempname()
  597. try
  598. execute cd.'`=s:repo().tree()`'
  599. let command = ''
  600. if &shell =~# 'cmd'
  601. let old_editor = $GIT_EDITOR
  602. let $GIT_EDITOR = 'false'
  603. elseif &shell !~# 'csh'
  604. let command = 'GIT_EDITOR=false '
  605. endif
  606. let command .= s:repo().git_command('commit').' '.a:args
  607. if &shell =~# 'csh'
  608. silent execute '!setenv GIT_EDITOR false; ('.command.' > '.outfile.') >& '.errorfile
  609. elseif a:args =~# '\%(^\| \)--interactive\>'
  610. execute '!'.command.' 2> '.errorfile
  611. else
  612. silent execute '!'.command.' > '.outfile.' 2> '.errorfile
  613. endif
  614. if !v:shell_error
  615. if filereadable(outfile)
  616. for line in readfile(outfile)
  617. echo line
  618. endfor
  619. endif
  620. return ''
  621. else
  622. let errors = readfile(errorfile)
  623. let error = get(errors,-2,get(errors,-1,'!'))
  624. if error =~# "'false'\\.$"
  625. let args = a:args
  626. let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[se]|--edit|--interactive)%($| )','')
  627. let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
  628. let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
  629. let args = '-F '.s:shellesc(msgfile).' '.args
  630. if args !~# '\%(^\| \)--cleanup\>'
  631. let args = '--cleanup=strip '.args
  632. endif
  633. if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
  634. edit `=msgfile`
  635. else
  636. split `=msgfile`
  637. endif
  638. if old_type ==# 'index'
  639. bdelete #
  640. endif
  641. let b:fugitive_commit_arguments = args
  642. setlocal bufhidden=delete filetype=gitcommit
  643. return '1'
  644. elseif error ==# '!'
  645. return s:Status()
  646. else
  647. call s:throw(error)
  648. endif
  649. endif
  650. catch /^fugitive:/
  651. return 'echoerr v:errmsg'
  652. finally
  653. if exists('old_editor')
  654. let $GIT_EDITOR = old_editor
  655. endif
  656. call delete(outfile)
  657. call delete(errorfile)
  658. execute cd.'`=dir`'
  659. call fugitive#reload_status()
  660. endtry
  661. endfunction
  662. function! s:CommitComplete(A,L,P) abort
  663. if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
  664. 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']
  665. return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
  666. else
  667. return s:repo().superglob(a:A)
  668. endif
  669. endfunction
  670. function! s:FinishCommit()
  671. let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
  672. if !empty(args)
  673. call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
  674. return s:Commit(args)
  675. endif
  676. return ''
  677. endfunction
  678. augroup fugitive_commit
  679. autocmd!
  680. autocmd VimLeavePre,BufDelete *.git/COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
  681. augroup END
  682. " }}}1
  683. " Ggrep, Glog {{{1
  684. if !exists('g:fugitive_summary_format')
  685. let g:fugitive_summary_format = '%s'
  686. endif
  687. call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep(<bang>0,<q-args>)")
  688. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Glog :execute s:Log('grep<bang>',<f-args>)")
  689. function! s:Grep(bang,arg) abort
  690. let grepprg = &grepprg
  691. let grepformat = &grepformat
  692. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  693. let dir = getcwd()
  694. try
  695. execute cd.'`=s:repo().tree()`'
  696. let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
  697. let &grepformat = '%f:%l:%m'
  698. exe 'grep! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
  699. let list = getqflist()
  700. for entry in list
  701. if bufname(entry.bufnr) =~ ':'
  702. let entry.filename = s:repo().translate(bufname(entry.bufnr))
  703. unlet! entry.bufnr
  704. elseif a:arg =~# '\%(^\| \)--cached\>'
  705. let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
  706. unlet! entry.bufnr
  707. endif
  708. endfor
  709. call setqflist(list,'r')
  710. if !a:bang && !empty(list)
  711. return 'cfirst'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
  712. else
  713. return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
  714. endif
  715. finally
  716. let &grepprg = grepprg
  717. let &grepformat = grepformat
  718. execute cd.'`=dir`'
  719. endtry
  720. endfunction
  721. function! s:Log(cmd,...)
  722. let path = s:buffer().path('/')
  723. if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
  724. let path = ''
  725. endif
  726. let cmd = ['--no-pager', 'log', '--no-color']
  727. let cmd += [escape('--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format,'%')]
  728. if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
  729. if s:buffer().commit() =~# '\x\{40\}'
  730. let cmd += [s:buffer().commit()]
  731. elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
  732. let cmd += [s:buffer().path()[5:-1]]
  733. endif
  734. end
  735. let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
  736. if path =~# '/.'
  737. let cmd += ['--',path[1:-1]]
  738. endif
  739. let grepformat = &grepformat
  740. let grepprg = &grepprg
  741. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  742. let dir = getcwd()
  743. try
  744. execute cd.'`=s:repo().tree()`'
  745. let &grepprg = call(s:repo().git_command,cmd,s:repo())
  746. let &grepformat = '%f::%m'
  747. exe a:cmd
  748. finally
  749. let &grepformat = grepformat
  750. let &grepprg = grepprg
  751. execute cd.'`=dir`'
  752. endtry
  753. endfunction
  754. " }}}1
  755. " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1
  756. function! s:Edit(cmd,...) abort
  757. if a:0 && a:1 == ''
  758. return ''
  759. elseif a:0
  760. let file = s:buffer().expand(a:1)
  761. elseif s:buffer().commit() ==# '' && s:buffer().path('/') !~# '^/.git\>'
  762. let file = s:buffer().path(':')
  763. else
  764. let file = s:buffer().path('/')
  765. endif
  766. try
  767. let file = s:repo().translate(file)
  768. catch /^fugitive:/
  769. return 'echoerr v:errmsg'
  770. endtry
  771. if a:cmd =~# 'read!$' || a:cmd ==# 'read'
  772. if a:cmd =~# '!$'
  773. call s:warn(':Gread! is deprecated. Use :Gread')
  774. endif
  775. return 'silent %delete|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
  776. else
  777. if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
  778. wincmd p
  779. endif
  780. return a:cmd.' '.s:fnameescape(file)
  781. endif
  782. endfunction
  783. function! s:EditComplete(A,L,P) abort
  784. return s:repo().superglob(a:A)
  785. endfunction
  786. call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',<f-args>)")
  787. call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',<f-args>)")
  788. call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gpedit :execute s:Edit('pedit<bang>',<f-args>)")
  789. call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gsplit :execute s:Edit('split<bang>',<f-args>)")
  790. call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gvsplit :execute s:Edit('vsplit<bang>',<f-args>)")
  791. call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gtabedit :execute s:Edit('tabedit<bang>',<f-args>)")
  792. call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read<bang>',<f-args>)")
  793. " }}}1
  794. " Gwrite {{{1
  795. call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
  796. function! s:Write(force,...) abort
  797. if exists('b:fugitive_commit_arguments')
  798. return 'write|bdelete'
  799. elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
  800. return 'wq'
  801. elseif s:buffer().type() == 'index'
  802. return 'Gcommit'
  803. endif
  804. let mytab = tabpagenr()
  805. let mybufnr = bufnr('')
  806. let path = a:0 ? a:1 : s:buffer().path()
  807. if path =~# '^:\d\>'
  808. return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
  809. endif
  810. let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
  811. 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) !=# ''
  812. let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
  813. return 'echoerr v:errmsg'
  814. endif
  815. let file = s:repo().translate(path)
  816. let treebufnr = 0
  817. for nr in range(1,bufnr('$'))
  818. if fnamemodify(bufname(nr),':p') ==# file
  819. let treebufnr = nr
  820. endif
  821. endfor
  822. if treebufnr > 0 && treebufnr != bufnr('')
  823. let temp = tempname()
  824. silent execute '%write '.temp
  825. for tab in [mytab] + range(1,tabpagenr('$'))
  826. for winnr in range(1,tabpagewinnr(tab,'$'))
  827. if tabpagebuflist(tab)[winnr-1] == treebufnr
  828. execute 'tabnext '.tab
  829. if winnr != winnr()
  830. execute winnr.'wincmd w'
  831. let restorewinnr = 1
  832. endif
  833. try
  834. let lnum = line('.')
  835. let last = line('$')
  836. silent execute '$read '.temp
  837. silent execute '1,'.last.'delete_'
  838. silent write!
  839. silent execute lnum
  840. let did = 1
  841. finally
  842. if exists('restorewinnr')
  843. wincmd p
  844. endif
  845. execute 'tabnext '.mytab
  846. endtry
  847. endif
  848. endfor
  849. endfor
  850. if !exists('did')
  851. call writefile(readfile(temp,'b'),file,'b')
  852. endif
  853. else
  854. execute 'write! '.s:fnameescape(s:repo().translate(path))
  855. endif
  856. if a:force
  857. let error = s:repo().git_chomp_in_tree('add', '--force', file)
  858. else
  859. let error = s:repo().git_chomp_in_tree('add', file)
  860. endif
  861. if v:shell_error
  862. let v:errmsg = 'fugitive: '.error
  863. return 'echoerr v:errmsg'
  864. endif
  865. if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
  866. set nomodified
  867. endif
  868. let one = s:repo().translate(':1:'.path)
  869. let two = s:repo().translate(':2:'.path)
  870. let three = s:repo().translate(':3:'.path)
  871. for nr in range(1,bufnr('$'))
  872. if bufloaded(nr) && !getbufvar(nr,'&modified') && (bufname(nr) == one || bufname(nr) == two || bufname(nr) == three)
  873. execute nr.'bdelete'
  874. endif
  875. endfor
  876. unlet! restorewinnr
  877. let zero = s:repo().translate(':0:'.path)
  878. for tab in range(1,tabpagenr('$'))
  879. for winnr in range(1,tabpagewinnr(tab,'$'))
  880. let bufnr = tabpagebuflist(tab)[winnr-1]
  881. let bufname = bufname(bufnr)
  882. if bufname ==# zero && bufnr != mybufnr
  883. execute 'tabnext '.tab
  884. if winnr != winnr()
  885. execute winnr.'wincmd w'
  886. let restorewinnr = 1
  887. endif
  888. try
  889. let lnum = line('.')
  890. let last = line('$')
  891. silent $read `=file`
  892. silent execute '1,'.last.'delete_'
  893. silent execute lnum
  894. set nomodified
  895. diffupdate
  896. finally
  897. if exists('restorewinnr')
  898. wincmd p
  899. endif
  900. execute 'tabnext '.mytab
  901. endtry
  902. break
  903. endif
  904. endfor
  905. endfor
  906. call fugitive#reload_status()
  907. return 'checktime'
  908. endfunction
  909. " }}}1
  910. " Gdiff {{{1
  911. call s:command("-bang -bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff(<bang>0,<f-args>)")
  912. call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gvdiff :execute s:Diff(0,<f-args>)")
  913. call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gsdiff :execute s:Diff(1,<f-args>)")
  914. augroup fugitive_diff
  915. autocmd!
  916. autocmd BufWinLeave * if s:diff_window_count() == 2 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diff_off_all(getbufvar(+expand('<abuf>'), 'git_dir')) | endif
  917. autocmd BufWinEnter * if s:diff_window_count() == 1 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | diffoff | endif
  918. augroup END
  919. function! s:diff_window_count()
  920. let c = 0
  921. for nr in range(1,winnr('$'))
  922. let c += getwinvar(nr,'&diff')
  923. endfor
  924. return c
  925. endfunction
  926. function! s:diff_off_all(dir)
  927. for nr in range(1,winnr('$'))
  928. if getwinvar(nr,'&diff')
  929. if nr != winnr()
  930. execute nr.'wincmd w'
  931. let restorewinnr = 1
  932. endif
  933. if exists('b:git_dir') && b:git_dir ==# a:dir
  934. diffoff
  935. endif
  936. if exists('restorewinnr')
  937. wincmd p
  938. endif
  939. endif
  940. endfor
  941. endfunction
  942. function! s:buffer_compare_age(commit) dict abort
  943. let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
  944. let my_score = get(scores,':'.self.commit(),0)
  945. let their_score = get(scores,':'.a:commit,0)
  946. if my_score || their_score
  947. return my_score < their_score ? -1 : my_score != their_score
  948. elseif self.commit() ==# a:commit
  949. return 0
  950. endif
  951. let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
  952. if base ==# self.commit()
  953. return -1
  954. elseif base ==# a:commit
  955. return 1
  956. endif
  957. let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
  958. let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
  959. return my_time < their_time ? -1 : my_time != their_time
  960. endfunction
  961. call s:add_methods('buffer',['compare_age'])
  962. function! s:Diff(bang,...) abort
  963. let split = a:bang ? 'split' : 'vsplit'
  964. if exists(':DiffGitCached')
  965. return 'DiffGitCached'
  966. elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
  967. execute 'leftabove '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
  968. diffthis
  969. wincmd p
  970. execute 'rightbelow '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
  971. diffthis
  972. wincmd p
  973. diffthis
  974. return ''
  975. elseif a:0
  976. if a:1 ==# ''
  977. return ''
  978. elseif a:1 ==# '/'
  979. let file = s:buffer().path('/')
  980. elseif a:1 ==# ':'
  981. let file = s:buffer().path(':0:')
  982. elseif a:1 =~# '^:/'
  983. try
  984. let file = s:repo().rev_parse(a:1).s:buffer().path(':')
  985. catch /^fugitive:/
  986. return 'echoerr v:errmsg'
  987. endtry
  988. else
  989. let file = s:buffer().expand(a:1)
  990. endif
  991. if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
  992. let file = file.s:buffer().path(':')
  993. endif
  994. else
  995. let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
  996. endif
  997. try
  998. let spec = s:repo().translate(file)
  999. let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
  1000. if s:buffer().compare_age(commit) < 0
  1001. execute 'rightbelow '.split.' `=spec`'
  1002. else
  1003. execute 'leftabove '.split.' `=spec`'
  1004. endif
  1005. diffthis
  1006. wincmd p
  1007. diffthis
  1008. return ''
  1009. catch /^fugitive:/
  1010. return 'echoerr v:errmsg'
  1011. endtry
  1012. endfunction
  1013. " }}}1
  1014. " Gmove, Gremove {{{1
  1015. function! s:Move(force,destination)
  1016. if a:destination =~# '^/'
  1017. let destination = a:destination[1:-1]
  1018. else
  1019. let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p')
  1020. if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
  1021. let destination = destination[strlen(s:repo().tree('')):-1]
  1022. endif
  1023. endif
  1024. let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
  1025. if v:shell_error
  1026. let v:errmsg = 'fugitive: '.message
  1027. return 'echoerr v:errmsg'
  1028. endif
  1029. let destination = s:repo().tree(destination)
  1030. if isdirectory(destination)
  1031. let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
  1032. endif
  1033. call fugitive#reload_status()
  1034. if s:buffer().commit() == ''
  1035. return 'saveas! '.s:fnameescape(destination)
  1036. else
  1037. return 'file '.s:fnameescape(s:repo().translate(':0:'.destination)
  1038. endif
  1039. endfunction
  1040. function! s:MoveComplete(A,L,P)
  1041. if a:A =~ '^/'
  1042. return s:repo().superglob(a:A)
  1043. else
  1044. let matches = split(glob(a:A.'*'),"\n")
  1045. call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
  1046. return matches
  1047. endif
  1048. endfunction
  1049. function! s:Remove(force)
  1050. if s:buffer().commit() ==# ''
  1051. let cmd = ['rm']
  1052. elseif s:buffer().commit() ==# '0'
  1053. let cmd = ['rm','--cached']
  1054. else
  1055. let v:errmsg = 'fugitive: rm not supported here'
  1056. return 'echoerr v:errmsg'
  1057. endif
  1058. if a:force
  1059. let cmd += ['--force']
  1060. endif
  1061. let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
  1062. if v:shell_error
  1063. let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
  1064. return 'echoerr '.string(v:errmsg)
  1065. else
  1066. call fugitive#reload_status()
  1067. return 'bdelete'.(a:force ? '!' : '')
  1068. endif
  1069. endfunction
  1070. augroup fugitive_remove
  1071. autocmd!
  1072. autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
  1073. \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
  1074. \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
  1075. \ endif
  1076. augroup END
  1077. " }}}1
  1078. " Gblame {{{1
  1079. augroup fugitive_blame
  1080. autocmd!
  1081. autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
  1082. autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
  1083. autocmd Syntax fugitiveblame call s:BlameSyntax()
  1084. 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
  1085. augroup END
  1086. function! s:Blame(bang,line1,line2,count,args) abort
  1087. try
  1088. if s:buffer().path() == ''
  1089. call s:throw('file or blob required')
  1090. endif
  1091. if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltwfs]\\|[MC]\\d*\\)\\+\\)$"') != []
  1092. call s:throw('unsupported option')
  1093. endif
  1094. call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
  1095. let git_dir = s:repo().dir()
  1096. let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
  1097. if s:buffer().commit() =~# '\D\|..'
  1098. let cmd += [s:buffer().commit()]
  1099. else
  1100. let cmd += ['--contents', '-']
  1101. endif
  1102. let basecmd = call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo())
  1103. try
  1104. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  1105. if !s:repo().bare()
  1106. let dir = getcwd()
  1107. execute cd.'`=s:repo().tree()`'
  1108. endif
  1109. if a:count
  1110. execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
  1111. else
  1112. let error = tempname()
  1113. let temp = error.'.fugitiveblame'
  1114. if &shell =~# 'csh'
  1115. silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
  1116. else
  1117. silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
  1118. endif
  1119. if exists('l:dir')
  1120. execute cd.'`=dir`'
  1121. unlet dir
  1122. endif
  1123. if v:shell_error
  1124. call s:throw(join(readfile(error),"\n"))
  1125. endif
  1126. let bufnr = bufnr('')
  1127. let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
  1128. if &l:wrap
  1129. let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
  1130. endif
  1131. if &l:foldenable
  1132. let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
  1133. endif
  1134. let winnr = winnr()
  1135. windo set noscrollbind
  1136. exe winnr.'wincmd w'
  1137. setlocal scrollbind nowrap nofoldenable
  1138. let top = line('w0') + &scrolloff
  1139. let current = line('.')
  1140. exe 'leftabove vsplit '.temp
  1141. let b:git_dir = git_dir
  1142. let b:fugitive_type = 'blame'
  1143. let b:fugitive_blamed_bufnr = bufnr
  1144. let w:fugitive_restore = restore
  1145. let b:fugitive_blame_arguments = join(a:args,' ')
  1146. call s:Detect(expand('%:p'))
  1147. execute top
  1148. normal! zt
  1149. execute current
  1150. execute "vertical resize ".(match(getline('.'),'\s\+\d\+)')+1)
  1151. setlocal nomodified nomodifiable bufhidden=delete nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame
  1152. nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
  1153. nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameJump('')<CR>
  1154. nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
  1155. nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
  1156. nnoremap <buffer> <silent> o :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft")." split", matchstr(getline('.'),'\x\+'))<CR>
  1157. nnoremap <buffer> <silent> O :<C-U>exe <SID>Edit("tabedit", matchstr(getline('.'),'\x\+'))<CR>
  1158. syncbind
  1159. endif
  1160. finally
  1161. if exists('l:dir')
  1162. execute cd.'`=dir`'
  1163. endif
  1164. endtry
  1165. return ''
  1166. catch /^fugitive:/
  1167. return 'echoerr v:errmsg'
  1168. endtry
  1169. endfunction
  1170. function! s:BlameJump(suffix) abort
  1171. let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
  1172. if commit =~# '^0\+$'
  1173. let commit = ':0'
  1174. endif
  1175. let lnum = matchstr(getline('.'),'\d\+\ze\s\+[([:digit:]]')
  1176. let path = matchstr(getline('.'),'^\^\=\zs\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
  1177. if path ==# ''
  1178. let path = s:buffer(b:fugitive_blamed_bufnr).path()
  1179. endif
  1180. let args = b:fugitive_blame_arguments
  1181. let offset = line('.') - line('w0')
  1182. let bufnr = bufnr('%')
  1183. let winnr = bufwinnr(b:fugitive_blamed_bufnr)
  1184. if winnr > 0
  1185. exe winnr.'wincmd w'
  1186. endif
  1187. execute s:Edit('edit',commit.a:suffix.':'.path)
  1188. if winnr > 0
  1189. exe bufnr.'bdelete'
  1190. endif
  1191. execute 'Gblame '.args
  1192. execute lnum
  1193. let delta = line('.') - line('w0') - offset
  1194. if delta > 0
  1195. execute 'norm! 'delta."\<C-E>"
  1196. elseif delta < 0
  1197. execute 'norm! '(-delta)."\<C-Y>"
  1198. endif
  1199. syncbind
  1200. return ''
  1201. endfunction
  1202. function! s:BlameSyntax() abort
  1203. let b:current_syntax = 'fugitiveblame'
  1204. syn match FugitiveblameBoundary "^\^"
  1205. syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
  1206. syn match FugitiveblameHash "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
  1207. syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
  1208. syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
  1209. syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
  1210. syn match FugitiveblameLineNumber " \@<=\d\+)\@=" contained containedin=FugitiveblameAnnotation
  1211. syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite
  1212. syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite
  1213. syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite
  1214. syn match FugitiveblameShort "\d\+)" contained contains=FugitiveblameLineNumber
  1215. syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
  1216. hi def link FugitiveblameBoundary Keyword
  1217. hi def link FugitiveblameHash Identifier
  1218. hi def link FugitiveblameUncommitted Function
  1219. hi def link FugitiveblameTime PreProc
  1220. hi def link FugitiveblameLineNumber Number
  1221. hi def link FugitiveblameOriginalFile String
  1222. hi def link FugitiveblameOriginalLineNumber Float
  1223. hi def link FugitiveblameShort FugitiveblameDelimiter
  1224. hi def link FugitiveblameDelimiter Delimiter
  1225. hi def link FugitiveblameNotCommittedYet Comment
  1226. endfunction
  1227. " }}}1
  1228. " File access {{{1
  1229. function! s:ReplaceCmd(cmd,...) abort
  1230. let fn = bufname('')
  1231. let tmp = tempname()
  1232. let aw = &autowrite
  1233. let prefix = ''
  1234. try
  1235. if a:0 && a:1 != ''
  1236. if &shell =~# 'cmd'
  1237. let old_index = $GIT_INDEX_FILE
  1238. let $GIT_INDEX_FILE = a:1
  1239. elseif &shell =~# 'csh'
  1240. let prefix = 'setenv GIT_INDEX_FILE '.s:shellesc(a:1).'; '
  1241. else
  1242. let prefix = 'GIT_INDEX_FILE='.s:shellesc(a:1).' '
  1243. endif
  1244. endif
  1245. set noautowrite
  1246. silent exe '!'.escape(prefix.a:cmd,'%#').' > '.tmp
  1247. finally
  1248. let &autowrite = aw
  1249. if exists('old_index')
  1250. let $GIT_INDEX_FILE = old_index
  1251. endif
  1252. endtry
  1253. silent exe 'keepalt file '.tmp
  1254. silent edit!
  1255. silent exe 'keepalt file '.s:fnameescape(fn)
  1256. call delete(tmp)
  1257. silent exe 'doau BufReadPost '.s:fnameescape(fn)
  1258. endfunction
  1259. function! s:BufReadIndex()
  1260. if !exists('b:fugitive_display_format')
  1261. let b:fugitive_display_format = filereadable(expand('%').'.lock')
  1262. endif
  1263. let b:fugitive_display_format = b:fugitive_display_format % 2
  1264. let b:fugitive_type = 'index'
  1265. try
  1266. let b:git_dir = s:repo().dir()
  1267. setlocal noro ma
  1268. if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
  1269. let index = ''
  1270. else
  1271. let index = expand('%:p')
  1272. endif
  1273. if b:fugitive_display_format
  1274. call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
  1275. set ft=git nospell
  1276. else
  1277. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  1278. let dir = getcwd()
  1279. try
  1280. execute cd.'`=s:repo().tree()`'
  1281. call s:ReplaceCmd(s:repo().git_command('status'),index)
  1282. finally
  1283. execute cd.'`=dir`'
  1284. endtry
  1285. set ft=gitcommit
  1286. endif
  1287. setlocal ro noma nomod nomodeline bufhidden=delete
  1288. nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
  1289. nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
  1290. nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('')<CR>
  1291. nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('')<CR>
  1292. nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('!')<CR>
  1293. nnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
  1294. xnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line("'<"),line("'>"))<CR>
  1295. nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
  1296. xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
  1297. nnoremap <buffer> <silent> <C-N> :call search('^#\t.*','W')<Bar>.<CR>
  1298. nnoremap <buffer> <silent> <C-P> :call search('^#\t.*','Wbe')<Bar>.<CR>
  1299. call s:JumpInit()
  1300. nunmap <buffer> P
  1301. nunmap <buffer> ~
  1302. nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
  1303. catch /^fugitive:/
  1304. return 'echoerr v:errmsg'
  1305. endtry
  1306. endfunction
  1307. function! s:FileRead()
  1308. try
  1309. let repo = s:repo(s:ExtractGitDir(expand('<amatch>')))
  1310. let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
  1311. let hash = repo.rev_parse(path)
  1312. if path =~ '^:'
  1313. let type = 'blob'
  1314. else
  1315. let type = repo.git_chomp('cat-file','-t',hash)
  1316. endif
  1317. " TODO: use count, if possible
  1318. return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
  1319. catch /^fugitive:/
  1320. return 'echoerr v:errmsg'
  1321. endtry
  1322. endfunction
  1323. function! s:BufReadIndexFile()
  1324. try
  1325. let b:fugitive_type = 'blob'
  1326. let b:git_dir = s:repo().dir()
  1327. call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
  1328. return ''
  1329. catch /^fugitive: rev-parse/
  1330. silent exe 'doau BufNewFile '.s:fnameescape(bufname(''))
  1331. return ''
  1332. catch /^fugitive:/
  1333. return 'echoerr v:errmsg'
  1334. endtry
  1335. endfunction
  1336. function! s:BufWriteIndexFile()
  1337. let tmp = tempname()
  1338. try
  1339. let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
  1340. let stage = matchstr(expand('<amatch>'),'//\zs\d')
  1341. silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
  1342. let sha1 = readfile(tmp)[0]
  1343. let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
  1344. if old_mode == ''
  1345. let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
  1346. endif
  1347. let info = old_mode.' '.sha1.' '.stage."\t".path
  1348. call writefile([info],tmp)
  1349. if has('win32')
  1350. let error = system('type '.tmp.'|'.s:repo().git_command('update-index','--index-info'))
  1351. else
  1352. let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
  1353. endif
  1354. if v:shell_error == 0
  1355. setlocal nomodified
  1356. silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
  1357. call fugitive#reload_status()
  1358. return ''
  1359. else
  1360. return 'echoerr '.string('fugitive: '.error)
  1361. endif
  1362. finally
  1363. call delete(tmp)
  1364. endtry
  1365. endfunction
  1366. function! s:BufReadObject()
  1367. try
  1368. setlocal noro ma
  1369. let b:git_dir = s:repo().dir()
  1370. let hash = s:buffer().sha1()
  1371. if !exists("b:fugitive_type")
  1372. let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
  1373. endif
  1374. if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
  1375. return "echoerr 'fugitive: unrecognized git type'"
  1376. endif
  1377. let firstline = getline('.')
  1378. if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
  1379. let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
  1380. endif
  1381. let pos = getpos('.')
  1382. silent %delete
  1383. setlocal endofline
  1384. if b:fugitive_type == 'tree'
  1385. let b:fugitive_display_format = b:fugitive_display_format % 2
  1386. if b:fugitive_display_format
  1387. call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
  1388. else
  1389. call s:ReplaceCmd(s:repo().git_command('show',hash))
  1390. endif
  1391. elseif b:fugitive_type == 'tag'
  1392. let b:fugitive_display_format = b:fugitive_display_format % 2
  1393. if b:fugitive_display_format
  1394. call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
  1395. else
  1396. call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
  1397. endif
  1398. elseif b:fugitive_type == 'commit'
  1399. let b:fugitive_display_format = b:fugitive_display_format % 2
  1400. if b:fugitive_display_format
  1401. call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
  1402. else
  1403. call s:ReplaceCmd(s:repo().git_command('show','--pretty=format:tree %T%nparent %P%nauthor %an <%ae> %ad%ncommitter %cn <%ce> %cd%nencoding %e%n%n%s%n%n%b',hash))
  1404. call search('^parent ')
  1405. if getline('.') ==# 'parent '
  1406. silent delete_
  1407. else
  1408. silent s/\%(^parent\)\@<! /\rparent /ge
  1409. endif
  1410. if search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
  1411. silent delete_
  1412. end
  1413. 1
  1414. endif
  1415. elseif b:fugitive_type ==# 'blob'
  1416. call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
  1417. endif
  1418. call setpos('.',pos)
  1419. setlocal ro noma nomod nomodeline
  1420. if b:fugitive_type !=# 'blob'
  1421. set filetype=git
  1422. nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
  1423. nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
  1424. else
  1425. call s:JumpInit()
  1426. endif
  1427. return ''
  1428. catch /^fugitive:/
  1429. return 'echoerr v:errmsg'
  1430. endtry
  1431. endfunction
  1432. augroup fugitive_files
  1433. autocmd!
  1434. autocmd BufReadCmd *.git/index exe s:BufReadIndex()
  1435. autocmd BufReadCmd *.git/*index*.lock exe s:BufReadIndex()
  1436. autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
  1437. autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
  1438. autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
  1439. autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
  1440. autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
  1441. autocmd FileType git call s:JumpInit()
  1442. augroup END
  1443. " }}}1
  1444. " Go to file {{{1
  1445. function! s:JumpInit() abort
  1446. nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
  1447. if !&modifiable
  1448. nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
  1449. nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
  1450. nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
  1451. nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
  1452. nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
  1453. nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
  1454. nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',<SID>buffer().containing_commit())<CR>
  1455. nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',<SID>buffer().containing_commit())<CR>
  1456. nnoremap <buffer> <silent> cp :<C-U>exe <SID>Edit('pedit',<SID>buffer().containing_commit())<CR>
  1457. endif
  1458. endfunction
  1459. function! s:GF(mode) abort
  1460. try
  1461. let buffer = s:buffer()
  1462. let myhash = buffer.sha1()
  1463. if buffer.type('tree')
  1464. let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
  1465. if showtree && line('.') == 1
  1466. return ""
  1467. elseif showtree && line('.') > 2
  1468. return s:Edit(a:mode,buffer.commit().':'.(buffer.path() == '' ? '' : buffer.path().'/').s:sub(getline('.'),'/$',''))
  1469. elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
  1470. return s:Edit(a:mode,buffer.commit().':'.(buffer.path() == '' ? '' : buffer.path().'/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
  1471. endif
  1472. elseif buffer.type('blob')
  1473. let ref = expand("<cfile>")
  1474. try
  1475. let sha1 = buffer.repo().rev_parse(ref)
  1476. catch /^fugitive:/
  1477. endtry
  1478. if exists('sha1')
  1479. return s:Edit(a:mode,ref)
  1480. endif
  1481. else
  1482. " Index
  1483. if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
  1484. let ref = matchstr(getline('.'),'\x\{40\}')
  1485. let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
  1486. return s:Edit(a:mode,file)
  1487. elseif getline('.') =~# '^#\trenamed:.* -> '
  1488. let file = '/'.matchstr(getline('.'),' -> \zs.*')
  1489. return s:Edit(a:mode,file)
  1490. elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
  1491. let file = '/'.matchstr(getline('.'),': *\zs.*')
  1492. return s:Edit(a:mode,file)
  1493. elseif getline('.') =~# '^#\t.'
  1494. let file = '/'.matchstr(getline('.'),'#\t\zs.*')
  1495. return s:Edit(a:mode,file)
  1496. elseif getline('.') =~# ': needs merge$'
  1497. let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
  1498. return s:Edit(a:mode,file).'|Gdiff'
  1499. elseif getline('.') ==# '# Not currently on any branch.'
  1500. return s:Edit(a:mode,'HEAD')
  1501. elseif getline('.') =~# '^# On branch '
  1502. let file = 'refs/heads/'.getline('.')[12:]
  1503. return s:Edit(a:mode,file)
  1504. elseif getline('.') =~# "^# Your branch .*'"
  1505. let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
  1506. return s:Edit(a:mode,file)
  1507. endif
  1508. let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
  1509. if getline('.') =~# '^ref: '
  1510. let ref = strpart(getline('.'),5)
  1511. elseif getline('.') =~# '^parent \x\{40\}\>'
  1512. let ref = matchstr(getline('.'),'\x\{40\}')
  1513. let line = line('.')
  1514. let parent = 0
  1515. while getline(line) =~# '^parent '
  1516. let parent += 1
  1517. let line -= 1
  1518. endwhile
  1519. return s:Edit(a:mode,ref)
  1520. elseif getline('.') =~ '^tree \x\{40\}$'
  1521. let ref = matchstr(getline('.'),'\x\{40\}')
  1522. if s:repo().rev_parse(myhash.':') == ref
  1523. let ref = myhash.':'
  1524. endif
  1525. return s:Edit(a:mode,ref)
  1526. elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
  1527. let ref = matchstr(getline('.'),'\x\{40\}')
  1528. let type = matchstr(getline(line('.')+1),'type \zs.*')
  1529. elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
  1530. return ''
  1531. elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
  1532. let ref = matchstr(getline('.'),'\x\{40\}')
  1533. echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
  1534. elseif getline('.') =~# '^[+-]\{3\} [ab/]'
  1535. let ref = getline('.')[4:]
  1536. elseif getline('.') =~# '^rename from '
  1537. let ref = 'a/'.getline('.')[12:]
  1538. elseif getline('.') =~# '^rename to '
  1539. let ref = 'b/'.getline('.')[10:]
  1540. elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
  1541. let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
  1542. let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
  1543. let dcmd = 'Gdiff'
  1544. elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
  1545. let line = getline(line('.')-1)
  1546. let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
  1547. let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
  1548. let dcmd = 'Gdiff!'
  1549. elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
  1550. let ref = getline('.')
  1551. else
  1552. let ref = ''
  1553. endif
  1554. if myhash ==# ''
  1555. let ref = s:sub(ref,'^a/','HEAD:')
  1556. let ref = s:sub(ref,'^b/',':0:')
  1557. if exists('dref')
  1558. let dref = s:sub(dref,'^a/','HEAD:')
  1559. endif
  1560. else
  1561. let ref = s:sub(ref,'^a/',myhash.'^:')
  1562. let ref = s:sub(ref,'^b/',myhash.':')
  1563. if exists('dref')
  1564. let dref = s:sub(dref,'^a/',myhash.'^:')
  1565. endif
  1566. endif
  1567. if ref ==# '/dev/null'
  1568. " Empty blob
  1569. let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
  1570. endif
  1571. if exists('dref')
  1572. return s:Edit(a:mode,ref) . '|'.dcmd.' '.s:fnameescape(dref)
  1573. elseif ref != ""
  1574. return s:Edit(a:mode,ref)
  1575. endif
  1576. endif
  1577. return ''
  1578. catch /^fugitive:/
  1579. return 'echoerr v:errmsg'
  1580. endtry
  1581. endfunction
  1582. " }}}1
  1583. " Statusline {{{1
  1584. function! s:repo_head_ref() dict abort
  1585. return readfile(s:repo().dir('HEAD'))[0]
  1586. endfunction
  1587. call s:add_methods('repo',['head_ref'])
  1588. function! fugitive#statusline(...)
  1589. if !exists('b:git_dir')
  1590. return ''
  1591. endif
  1592. let status = ''
  1593. if s:buffer().commit() != ''
  1594. let status .= ':' . s:buffer().commit()[0:7]
  1595. endif
  1596. let head = s:repo().head_ref()
  1597. if head =~# '^ref: '
  1598. let status .= s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','(').')'
  1599. elseif head =~# '^\x\{40\}$'
  1600. let status .= '('.head[0:7].')'
  1601. endif
  1602. if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
  1603. return ',GIT'.status
  1604. else
  1605. return '[Git'.status.']'
  1606. endif
  1607. endfunction
  1608. function! s:repo_config(conf) dict abort
  1609. return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
  1610. endfun
  1611. function! s:repo_user() dict abort
  1612. let username = s:repo().config('user.name')
  1613. let useremail = s:repo().config('user.email')
  1614. return username.' <'.useremail.'>'
  1615. endfun
  1616. call s:add_methods('repo',['config', 'user'])
  1617. " }}}1
  1618. " vim:set ft=vim ts=8 sw=2 sts=2: