fugitive.vim 66 KB

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