fugitive.vim 103 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126
  1. " fugitive.vim - A Git wrapper so awesome, it should be illegal
  2. " Maintainer: Tim Pope <http://tpo.pe/>
  3. " Version: 2.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. " Section: Utility
  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:winshell() abort
  23. return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
  24. endfunction
  25. function! s:shellesc(arg) abort
  26. if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
  27. return a:arg
  28. elseif s:winshell()
  29. return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
  30. else
  31. return shellescape(a:arg)
  32. endif
  33. endfunction
  34. function! s:fnameescape(file) abort
  35. if exists('*fnameescape')
  36. return fnameescape(a:file)
  37. else
  38. return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
  39. endif
  40. endfunction
  41. function! s:throw(string) abort
  42. let v:errmsg = 'fugitive: '.a:string
  43. throw v:errmsg
  44. endfunction
  45. function! s:warn(str) abort
  46. echohl WarningMsg
  47. echomsg a:str
  48. echohl None
  49. let v:warningmsg = a:str
  50. endfunction
  51. function! s:shellslash(path) abort
  52. if s:winshell()
  53. return s:gsub(a:path,'\\','/')
  54. else
  55. return a:path
  56. endif
  57. endfunction
  58. let s:executables = {}
  59. function! s:executable(binary) abort
  60. if !has_key(s:executables, a:binary)
  61. let s:executables[a:binary] = executable(a:binary)
  62. endif
  63. return s:executables[a:binary]
  64. endfunction
  65. let s:git_versions = {}
  66. function! s:git_command() abort
  67. return get(g:, 'fugitive_git_command', g:fugitive_git_executable)
  68. endfunction
  69. function! fugitive#git_version(...) abort
  70. if !has_key(s:git_versions, g:fugitive_git_executable)
  71. let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\n")
  72. endif
  73. return s:git_versions[g:fugitive_git_executable]
  74. endfunction
  75. function! s:recall() abort
  76. let rev = s:sub(s:buffer().rev(), '^/', '')
  77. if rev ==# ':'
  78. return matchstr(getline('.'),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
  79. elseif s:buffer().type('tree')
  80. let file = matchstr(getline('.'), '\t\zs.*')
  81. if empty(file) && line('.') > 2
  82. let file = s:sub(getline('.'), '/$', '')
  83. endif
  84. if !empty(file) && rev !~# ':$'
  85. return rev . '/' . file
  86. else
  87. return rev . file
  88. endif
  89. endif
  90. return rev
  91. endfunction
  92. function! s:add_methods(namespace, method_names) abort
  93. for name in a:method_names
  94. let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
  95. endfor
  96. endfunction
  97. let s:commands = []
  98. function! s:command(definition) abort
  99. let s:commands += [a:definition]
  100. endfunction
  101. function! s:define_commands() abort
  102. for command in s:commands
  103. exe 'command! -buffer '.command
  104. endfor
  105. endfunction
  106. let s:abstract_prototype = {}
  107. " Section: Initialization
  108. function! fugitive#is_git_dir(path) abort
  109. let path = s:sub(a:path, '[\/]$', '') . '/'
  110. return getfsize(path.'HEAD') > 10 && (
  111. \ isdirectory(path.'objects') && isdirectory(path.'refs') ||
  112. \ getftype(path.'commondir') ==# 'file')
  113. endfunction
  114. function! fugitive#extract_git_dir(path) abort
  115. if s:shellslash(a:path) =~# '^fugitive://.*//'
  116. return matchstr(s:shellslash(a:path), '\C^fugitive://\zs.\{-\}\ze//')
  117. endif
  118. if isdirectory(a:path)
  119. let path = fnamemodify(a:path, ':p:s?[\/]$??')
  120. else
  121. let path = fnamemodify(a:path, ':p:h:s?[\/]$??')
  122. endif
  123. let root = s:shellslash(resolve(path))
  124. let previous = ""
  125. while root !=# previous
  126. if root =~# '\v^//%([^/]+/?)?$'
  127. " This is for accessing network shares from Cygwin Vim. There won't be
  128. " any git directory called //.git or //serverName/.git so let's avoid
  129. " checking for them since such checks are extremely slow.
  130. break
  131. endif
  132. if index(split($GIT_CEILING_DIRECTORIES, ':'), root) >= 0
  133. break
  134. endif
  135. if root ==# $GIT_WORK_TREE && fugitive#is_git_dir($GIT_DIR)
  136. return simplify(fnamemodify(expand($GIT_DIR), ':p:s?[\/]$??'))
  137. endif
  138. if fugitive#is_git_dir($GIT_DIR)
  139. " Ensure that we've cached the worktree
  140. call s:configured_tree(simplify(fnamemodify(expand($GIT_DIR), ':p:s?[\/]$??')))
  141. if has_key(s:dir_for_worktree, root)
  142. return s:dir_for_worktree[root]
  143. endif
  144. endif
  145. let dir = s:sub(root, '[\/]$', '') . '/.git'
  146. let type = getftype(dir)
  147. if type ==# 'dir' && fugitive#is_git_dir(dir)
  148. return dir
  149. elseif type ==# 'link' && fugitive#is_git_dir(dir)
  150. return resolve(dir)
  151. elseif type !=# '' && filereadable(dir)
  152. let line = get(readfile(dir, '', 1), 0, '')
  153. if line =~# '^gitdir: \.' && fugitive#is_git_dir(root.'/'.line[8:-1])
  154. return simplify(root.'/'.line[8:-1])
  155. elseif line =~# '^gitdir: ' && fugitive#is_git_dir(line[8:-1])
  156. return line[8:-1]
  157. endif
  158. elseif fugitive#is_git_dir(root)
  159. return root
  160. endif
  161. let previous = root
  162. let root = fnamemodify(root, ':h')
  163. endwhile
  164. return ''
  165. endfunction
  166. function! fugitive#detect(path) abort
  167. if exists('b:git_dir') && (b:git_dir ==# '' || b:git_dir =~# '/$')
  168. unlet b:git_dir
  169. endif
  170. if !exists('b:git_dir')
  171. let dir = fugitive#extract_git_dir(a:path)
  172. if dir !=# ''
  173. let b:git_dir = dir
  174. if empty(fugitive#buffer().path())
  175. silent! exe haslocaldir() ? 'lcd .' : 'cd .'
  176. endif
  177. endif
  178. endif
  179. if exists('b:git_dir')
  180. if exists('#User#FugitiveBoot')
  181. try
  182. let [save_mls, &modelines] = [&mls, 0]
  183. doautocmd User FugitiveBoot
  184. finally
  185. let &mls = save_mls
  186. endtry
  187. endif
  188. if !exists('g:fugitive_no_maps')
  189. cnoremap <buffer> <expr> <C-R><C-G> fnameescape(<SID>recall())
  190. nnoremap <buffer> <silent> y<C-G> :call setreg(v:register, <SID>recall())<CR>
  191. endif
  192. let buffer = fugitive#buffer()
  193. if expand('%:p') =~# '://'
  194. call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
  195. endif
  196. if stridx(buffer.getvar('&tags'), escape(b:git_dir, ', ')) == -1
  197. if filereadable(b:git_dir.'/tags')
  198. call buffer.setvar('&tags', escape(b:git_dir.'/tags', ', ').','.buffer.getvar('&tags'))
  199. endif
  200. if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
  201. call buffer.setvar('&tags', escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.buffer.getvar('&tags'))
  202. endif
  203. endif
  204. try
  205. let [save_mls, &modelines] = [&mls, 0]
  206. call s:define_commands()
  207. doautocmd User Fugitive
  208. finally
  209. let &mls = save_mls
  210. endtry
  211. endif
  212. endfunction
  213. augroup fugitive
  214. autocmd!
  215. autocmd BufNewFile,BufReadPost * call fugitive#detect(expand('%:p'))
  216. autocmd FileType netrw call fugitive#detect(expand('%:p'))
  217. autocmd User NERDTreeInit,NERDTreeNewRoot call fugitive#detect(b:NERDTreeRoot.path.str())
  218. autocmd VimEnter * if expand('<amatch>')==''|call fugitive#detect(getcwd())|endif
  219. autocmd CmdWinEnter * call fugitive#detect(expand('#:p'))
  220. autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
  221. augroup END
  222. " Section: Repository
  223. let s:repo_prototype = {}
  224. let s:repos = {}
  225. let s:worktree_for_dir = {}
  226. let s:dir_for_worktree = {}
  227. function! s:repo(...) abort
  228. let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
  229. if dir !=# ''
  230. if has_key(s:repos, dir)
  231. let repo = get(s:repos, dir)
  232. else
  233. let repo = {'git_dir': dir}
  234. let s:repos[dir] = repo
  235. endif
  236. return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
  237. endif
  238. call s:throw('not a git repository: '.expand('%:p'))
  239. endfunction
  240. function! fugitive#repo(...) abort
  241. return call('s:repo', a:000)
  242. endfunction
  243. function! s:repo_dir(...) dict abort
  244. return join([self.git_dir]+a:000,'/')
  245. endfunction
  246. function! s:configured_tree(git_dir) abort
  247. if !has_key(s:worktree_for_dir, a:git_dir)
  248. let s:worktree_for_dir[a:git_dir] = ''
  249. let config_file = a:git_dir . '/config'
  250. if filereadable(config_file)
  251. let config = readfile(config_file,'',10)
  252. call filter(config,'v:val =~# "^\\s*worktree *="')
  253. if len(config) == 1
  254. let worktree = matchstr(config[0], '= *\zs.*')
  255. endif
  256. elseif filereadable(a:git_dir . '/gitdir')
  257. let worktree = fnamemodify(readfile(a:git_dir . '/gitdir')[0], ':h')
  258. if worktree ==# '.'
  259. unlet! worktree
  260. endif
  261. endif
  262. if exists('worktree')
  263. let s:worktree_for_dir[a:git_dir] = worktree
  264. let s:dir_for_worktree[s:worktree_for_dir[a:git_dir]] = a:git_dir
  265. endif
  266. endif
  267. if s:worktree_for_dir[a:git_dir] =~# '^\.'
  268. return simplify(a:git_dir . '/' . s:worktree_for_dir[a:git_dir])
  269. else
  270. return s:worktree_for_dir[a:git_dir]
  271. endif
  272. endfunction
  273. function! s:repo_tree(...) dict abort
  274. if self.dir() =~# '/\.git$'
  275. let dir = self.dir()[0:-6]
  276. if dir !~# '/'
  277. let dir .= '/'
  278. endif
  279. else
  280. let dir = s:configured_tree(self.git_dir)
  281. endif
  282. if dir ==# ''
  283. call s:throw('no work tree')
  284. else
  285. return join([dir]+a:000,'/')
  286. endif
  287. endfunction
  288. function! s:repo_bare() dict abort
  289. if self.dir() =~# '/\.git$'
  290. return 0
  291. else
  292. return s:configured_tree(self.git_dir) ==# ''
  293. endif
  294. endfunction
  295. function! s:repo_translate(spec) dict abort
  296. let refs = self.dir('refs/')
  297. if filereadable(self.dir('commondir'))
  298. let refs = simplify(self.dir(get(readfile(self.dir('commondir'), 1), 0, ''))) . '/refs/'
  299. endif
  300. if a:spec ==# '.' || a:spec ==# '/.'
  301. return self.bare() ? self.dir() : self.tree()
  302. elseif a:spec =~# '^/\=\.git$' && self.bare()
  303. return self.dir()
  304. elseif a:spec =~# '^/\=\.git/'
  305. return self.dir(s:sub(a:spec, '^/=\.git/', ''))
  306. elseif a:spec =~# '^/'
  307. return self.tree().a:spec
  308. elseif a:spec =~# '^:[0-3]:'
  309. return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
  310. elseif a:spec ==# ':'
  311. if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
  312. return fnamemodify($GIT_INDEX_FILE,':p')
  313. else
  314. return self.dir('index')
  315. endif
  316. elseif a:spec =~# '^:/'
  317. let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
  318. return 'fugitive://'.self.dir().'//'.ref
  319. elseif a:spec =~# '^:'
  320. return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
  321. elseif a:spec ==# '@'
  322. return self.dir('HEAD')
  323. elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(refs . '../' . a:spec)
  324. return simplify(refs . '../' . a:spec)
  325. elseif filereadable(refs.a:spec)
  326. return refs.a:spec
  327. elseif filereadable(refs.'tags/'.a:spec)
  328. return refs.'tags/'.a:spec
  329. elseif filereadable(refs.'heads/'.a:spec)
  330. return refs.'heads/'.a:spec
  331. elseif filereadable(refs.'remotes/'.a:spec)
  332. return refs.'remotes/'.a:spec
  333. elseif filereadable(refs.'remotes/'.a:spec.'/HEAD')
  334. return refs.'remotes/'.a:spec.'/HEAD'
  335. else
  336. try
  337. let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
  338. let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
  339. return 'fugitive://'.self.dir().'//'.ref.path
  340. catch /^fugitive:/
  341. return self.tree(a:spec)
  342. endtry
  343. endif
  344. endfunction
  345. function! s:repo_head(...) dict abort
  346. let head = s:repo().head_ref()
  347. if head =~# '^ref: '
  348. let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
  349. elseif head =~# '^\x\{40\}$'
  350. " truncate hash to a:1 characters if we're in detached head mode
  351. let len = a:0 ? a:1 : 0
  352. let branch = len ? head[0:len-1] : ''
  353. else
  354. return ''
  355. endif
  356. return branch
  357. endfunction
  358. call s:add_methods('repo',['dir','tree','bare','translate','head'])
  359. function! s:repo_git_command(...) dict abort
  360. let git = s:git_command() . ' --git-dir='.s:shellesc(self.git_dir)
  361. return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
  362. endfunction
  363. function! s:repo_git_chomp(...) dict abort
  364. let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
  365. let output = git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
  366. return s:sub(system(output),'\n$','')
  367. endfunction
  368. function! s:repo_git_chomp_in_tree(...) dict abort
  369. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  370. let dir = getcwd()
  371. try
  372. execute cd s:fnameescape(s:repo().tree())
  373. return call(s:repo().git_chomp, a:000, s:repo())
  374. finally
  375. execute cd s:fnameescape(dir)
  376. endtry
  377. endfunction
  378. function! s:repo_rev_parse(rev) dict abort
  379. let hash = self.git_chomp('rev-parse','--verify',a:rev)
  380. if hash =~ '\<\x\{40\}$'
  381. return matchstr(hash,'\<\x\{40\}$')
  382. endif
  383. call s:throw('rev-parse '.a:rev.': '.hash)
  384. endfunction
  385. call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
  386. function! s:repo_dirglob(base) dict abort
  387. let base = s:sub(a:base,'^/','')
  388. let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
  389. call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
  390. return matches
  391. endfunction
  392. function! s:repo_superglob(base) dict abort
  393. if a:base =~# '^/' || a:base !~# ':'
  394. let results = []
  395. if a:base !~# '^/'
  396. let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
  397. let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
  398. " Add any stashes.
  399. if filereadable(s:repo().dir('refs/stash'))
  400. let heads += ["stash"]
  401. let heads += sort(split(s:repo().git_chomp("stash","list","--pretty=format:%gd"),"\n"))
  402. endif
  403. call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
  404. let results += heads
  405. endif
  406. if !self.bare()
  407. let base = s:sub(a:base,'^/','')
  408. let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
  409. call map(matches,'s:shellslash(v:val)')
  410. call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
  411. call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
  412. let results += matches
  413. endif
  414. return results
  415. elseif a:base =~# '^:'
  416. let entries = split(self.git_chomp('ls-files','--stage'),"\n")
  417. call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
  418. if a:base !~# '^:[0-3]\%(:\|$\)'
  419. call filter(entries,'v:val[1] == "0"')
  420. call map(entries,'v:val[2:-1]')
  421. endif
  422. call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
  423. return entries
  424. else
  425. let tree = matchstr(a:base,'.*[:/]')
  426. let entries = split(self.git_chomp('ls-tree',tree),"\n")
  427. call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
  428. call map(entries,'tree.s:sub(v:val,".*\t","")')
  429. return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
  430. endif
  431. endfunction
  432. call s:add_methods('repo',['dirglob','superglob'])
  433. function! s:repo_config(conf) dict abort
  434. return matchstr(s:repo().git_chomp('config',a:conf),"[^\r\n]*")
  435. endfun
  436. function! s:repo_user() dict abort
  437. let username = s:repo().config('user.name')
  438. let useremail = s:repo().config('user.email')
  439. return username.' <'.useremail.'>'
  440. endfun
  441. function! s:repo_aliases() dict abort
  442. if !has_key(self,'_aliases')
  443. let self._aliases = {}
  444. for line in split(self.git_chomp('config','-z','--get-regexp','^alias[.]'),"\1")
  445. let self._aliases[matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
  446. endfor
  447. endif
  448. return self._aliases
  449. endfunction
  450. call s:add_methods('repo',['config', 'user', 'aliases'])
  451. function! s:repo_keywordprg() dict abort
  452. let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
  453. if has('gui_running') && !has('win32')
  454. return s:git_command() . ' --no-pager' . args . ' log -1'
  455. else
  456. return s:git_command() . args . ' show'
  457. endif
  458. endfunction
  459. call s:add_methods('repo',['keywordprg'])
  460. " Section: Buffer
  461. let s:buffer_prototype = {}
  462. function! s:buffer(...) abort
  463. let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
  464. call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
  465. if buffer.getvar('git_dir') !=# ''
  466. return buffer
  467. endif
  468. call s:throw('not a git repository: '.expand('%:p'))
  469. endfunction
  470. function! fugitive#buffer(...) abort
  471. return s:buffer(a:0 ? a:1 : '%')
  472. endfunction
  473. function! s:buffer_getvar(var) dict abort
  474. return getbufvar(self['#'],a:var)
  475. endfunction
  476. function! s:buffer_setvar(var,value) dict abort
  477. return setbufvar(self['#'],a:var,a:value)
  478. endfunction
  479. function! s:buffer_getline(lnum) dict abort
  480. return get(getbufline(self['#'], a:lnum), 0, '')
  481. endfunction
  482. function! s:buffer_repo() dict abort
  483. return s:repo(self.getvar('git_dir'))
  484. endfunction
  485. function! s:buffer_type(...) dict abort
  486. if self.getvar('fugitive_type') != ''
  487. let type = self.getvar('fugitive_type')
  488. elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
  489. let type = 'head'
  490. elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
  491. let type = 'tree'
  492. elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
  493. let type = 'tree'
  494. elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
  495. let type = 'index'
  496. elseif isdirectory(self.spec())
  497. let type = 'directory'
  498. elseif self.spec() == ''
  499. let type = 'null'
  500. else
  501. let type = 'file'
  502. endif
  503. if a:0
  504. return !empty(filter(copy(a:000),'v:val ==# type'))
  505. else
  506. return type
  507. endif
  508. endfunction
  509. if has('win32')
  510. function! s:buffer_spec() dict abort
  511. let bufname = bufname(self['#'])
  512. let retval = ''
  513. for i in split(bufname,'[^:]\zs\\')
  514. let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
  515. endfor
  516. return s:shellslash(fnamemodify(retval,':p'))
  517. endfunction
  518. else
  519. function! s:buffer_spec() dict abort
  520. let bufname = bufname(self['#'])
  521. return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
  522. endfunction
  523. endif
  524. function! s:buffer_name() dict abort
  525. return self.spec()
  526. endfunction
  527. function! s:buffer_commit() dict abort
  528. return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
  529. endfunction
  530. function! s:cpath(path) abort
  531. if exists('+fileignorecase') && &fileignorecase
  532. return tolower(a:path)
  533. else
  534. return a:path
  535. endif
  536. endfunction
  537. function! s:buffer_path(...) dict abort
  538. let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
  539. if rev != ''
  540. let rev = s:sub(rev,'\w*','')
  541. elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
  542. \ s:cpath(self.repo().dir() . '/')
  543. let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
  544. elseif !self.repo().bare() &&
  545. \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
  546. \ s:cpath(self.repo().tree() . '/')
  547. let rev = self.spec()[strlen(self.repo().tree()) : -1]
  548. endif
  549. return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
  550. endfunction
  551. function! s:buffer_rev() dict abort
  552. let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
  553. if rev =~ '^\x/'
  554. return ':'.rev[0].':'.rev[2:-1]
  555. elseif rev =~ '.'
  556. return s:sub(rev,'/',':')
  557. elseif self.spec() =~ '\.git/index$'
  558. return ':'
  559. elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
  560. return self.spec()[strlen(self.repo().dir())+1 : -1]
  561. else
  562. return self.path('/')
  563. endif
  564. endfunction
  565. function! s:buffer_sha1() dict abort
  566. if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
  567. return self.repo().rev_parse(self.rev())
  568. else
  569. return ''
  570. endif
  571. endfunction
  572. function! s:buffer_expand(rev) dict abort
  573. if a:rev =~# '^:[0-3]$'
  574. let file = a:rev.self.path(':')
  575. elseif a:rev =~# '^[-:]/$'
  576. let file = '/'.self.path()
  577. elseif a:rev =~# '^-'
  578. let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
  579. elseif a:rev =~# '^@{'
  580. let file = 'HEAD'.a:rev.self.path(':')
  581. elseif a:rev =~# '^[~^]'
  582. let commit = s:sub(self.commit(),'^\d=$','HEAD')
  583. let file = commit.a:rev.self.path(':')
  584. else
  585. let file = a:rev
  586. endif
  587. return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
  588. endfunction
  589. function! s:buffer_containing_commit() dict abort
  590. if self.commit() =~# '^\d$'
  591. return ':'
  592. elseif self.commit() =~# '.'
  593. return self.commit()
  594. else
  595. return 'HEAD'
  596. endif
  597. endfunction
  598. function! s:buffer_up(...) dict abort
  599. let rev = self.rev()
  600. let c = a:0 ? a:1 : 1
  601. while c
  602. if rev =~# '^[/:]$'
  603. let rev = 'HEAD'
  604. elseif rev =~# '^:'
  605. let rev = ':'
  606. elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
  607. let rev .= '^{}'
  608. elseif rev =~# '^/\|:.*/'
  609. let rev = s:sub(rev, '.*\zs/.*', '')
  610. elseif rev =~# ':.'
  611. let rev = matchstr(rev, '^[^:]*:')
  612. elseif rev =~# ':$'
  613. let rev = rev[0:-2]
  614. else
  615. return rev.'~'.c
  616. endif
  617. let c -= 1
  618. endwhile
  619. return rev
  620. endfunction
  621. call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
  622. " Section: Git
  623. call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
  624. function! s:ExecuteInTree(cmd) abort
  625. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  626. let dir = getcwd()
  627. try
  628. execute cd s:fnameescape(s:repo().tree())
  629. execute a:cmd
  630. finally
  631. execute cd s:fnameescape(dir)
  632. endtry
  633. endfunction
  634. function! s:Git(bang, args) abort
  635. if a:bang
  636. return s:Edit('edit', 1, a:args)
  637. endif
  638. let git = s:git_command()
  639. if has('gui_running') && !has('win32')
  640. let git .= ' --no-pager'
  641. endif
  642. let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
  643. if exists(':terminal') && has('nvim')
  644. let dir = s:repo().tree()
  645. if expand('%') != ''
  646. -tabedit %
  647. else
  648. -tabnew
  649. endif
  650. execute 'lcd' fnameescape(dir)
  651. execute 'terminal' git args
  652. else
  653. call s:ExecuteInTree('!'.git.' '.args)
  654. if has('win32')
  655. call fugitive#reload_status()
  656. endif
  657. endif
  658. return matchstr(a:args, '\v\C\\@<!%(\\\\)*\|\zs.*')
  659. endfunction
  660. function! fugitive#git_commands() abort
  661. if !exists('s:exec_path')
  662. let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
  663. endif
  664. return map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
  665. endfunction
  666. function! s:GitComplete(A, L, P) abort
  667. if strpart(a:L, 0, a:P) !~# ' [[:alnum:]-]\+ '
  668. let cmds = fugitive#git_commands()
  669. return filter(sort(cmds+keys(s:repo().aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
  670. else
  671. return s:repo().superglob(a:A)
  672. endif
  673. endfunction
  674. " Section: Gcd, Glcd
  675. function! s:DirComplete(A,L,P) abort
  676. let matches = s:repo().dirglob(a:A)
  677. return matches
  678. endfunction
  679. call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :exe 'cd<bang>' s:fnameescape(s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>))")
  680. call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>))")
  681. " Section: Gstatus
  682. call s:command("-bar Gstatus :execute s:Status()")
  683. augroup fugitive_status
  684. autocmd!
  685. if !has('win32')
  686. autocmd FocusGained,ShellCmdPost * call fugitive#reload_status()
  687. autocmd BufDelete term://* call fugitive#reload_status()
  688. endif
  689. augroup END
  690. function! s:Status() abort
  691. try
  692. Gpedit :
  693. wincmd P
  694. setlocal foldmethod=syntax foldlevel=1
  695. nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
  696. catch /^fugitive:/
  697. return 'echoerr v:errmsg'
  698. endtry
  699. return ''
  700. endfunction
  701. function! fugitive#reload_status() abort
  702. if exists('s:reloading_status')
  703. return
  704. endif
  705. try
  706. let s:reloading_status = 1
  707. let mytab = tabpagenr()
  708. for tab in [mytab] + range(1,tabpagenr('$'))
  709. for winnr in range(1,tabpagewinnr(tab,'$'))
  710. if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
  711. execute 'tabnext '.tab
  712. if winnr != winnr()
  713. execute winnr.'wincmd w'
  714. let restorewinnr = 1
  715. endif
  716. try
  717. if !&modified
  718. call s:BufReadIndex()
  719. endif
  720. finally
  721. if exists('restorewinnr')
  722. wincmd p
  723. endif
  724. execute 'tabnext '.mytab
  725. endtry
  726. endif
  727. endfor
  728. endfor
  729. finally
  730. unlet! s:reloading_status
  731. endtry
  732. endfunction
  733. function! s:stage_info(lnum) abort
  734. let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
  735. let lnum = a:lnum
  736. if has('multi_byte_encoding')
  737. let colon = '\%(:\|\%uff1a\)'
  738. else
  739. let colon = ':'
  740. endif
  741. while lnum && getline(lnum) !~# colon.'$'
  742. let lnum -= 1
  743. endwhile
  744. if !lnum
  745. return ['', '']
  746. elseif (getline(lnum+1) =~# '^# .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) ==# '# Changes to be committed:'
  747. return [matchstr(filename, colon.' *\zs.*'), 'staged']
  748. elseif (getline(lnum+1) =~# '^# .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) ==# '# Untracked files:'
  749. return [filename, 'untracked']
  750. elseif getline(lnum+2) =~# '^# .*\<git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
  751. return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
  752. elseif getline(lnum+2) =~# '^# .*\<git \%(add\|rm\)' || getline(lnum) ==# '# Unmerged paths:'
  753. return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
  754. else
  755. return ['', 'unknown']
  756. endif
  757. endfunction
  758. function! s:StageNext(count) abort
  759. for i in range(a:count)
  760. call search('^#\t.*','W')
  761. endfor
  762. return '.'
  763. endfunction
  764. function! s:StagePrevious(count) abort
  765. if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
  766. return 'CtrlP '.fnameescape(s:repo().tree())
  767. else
  768. for i in range(a:count)
  769. call search('^#\t.*','Wbe')
  770. endfor
  771. return '.'
  772. endif
  773. endfunction
  774. function! s:StageReloadSeek(target,lnum1,lnum2) abort
  775. let jump = a:target
  776. let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
  777. if f !=# '' | let jump = f | endif
  778. let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
  779. if f !=# '' | let jump = f | endif
  780. silent! edit!
  781. 1
  782. redraw
  783. call search('^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
  784. endfunction
  785. function! s:StageUndo() abort
  786. let [filename, section] = s:stage_info(line('.'))
  787. if empty(filename)
  788. return ''
  789. endif
  790. let repo = s:repo()
  791. let hash = repo.git_chomp('hash-object', '-w', filename)
  792. if !empty(hash)
  793. if section ==# 'untracked'
  794. call repo.git_chomp_in_tree('clean', '-f', '--', filename)
  795. elseif section ==# 'unmerged'
  796. call repo.git_chomp_in_tree('rm', '--', filename)
  797. elseif section ==# 'unstaged'
  798. call repo.git_chomp_in_tree('checkout', '--', filename)
  799. else
  800. call repo.git_chomp_in_tree('checkout', 'HEAD', '--', filename)
  801. endif
  802. call s:StageReloadSeek(filename, line('.'), line('.'))
  803. let @" = hash
  804. return 'checktime|redraw|echomsg ' .
  805. \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
  806. endif
  807. endfunction
  808. function! s:StageDiff(diff) abort
  809. let [filename, section] = s:stage_info(line('.'))
  810. if filename ==# '' && section ==# 'staged'
  811. return 'Git! diff --no-ext-diff --cached'
  812. elseif filename ==# ''
  813. return 'Git! diff --no-ext-diff'
  814. elseif filename =~# ' -> '
  815. let [old, new] = split(filename,' -> ')
  816. execute 'Gedit '.s:fnameescape(':0:'.new)
  817. return a:diff.' HEAD:'.s:fnameescape(old)
  818. elseif section ==# 'staged'
  819. execute 'Gedit '.s:fnameescape(':0:'.filename)
  820. return a:diff.' -'
  821. else
  822. execute 'Gedit '.s:fnameescape('/'.filename)
  823. return a:diff
  824. endif
  825. endfunction
  826. function! s:StageDiffEdit() abort
  827. let [filename, section] = s:stage_info(line('.'))
  828. let arg = (filename ==# '' ? '.' : filename)
  829. if section ==# 'staged'
  830. return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
  831. elseif section ==# 'untracked'
  832. let repo = s:repo()
  833. call repo.git_chomp_in_tree('add','--intent-to-add',arg)
  834. if arg ==# '.'
  835. silent! edit!
  836. 1
  837. if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
  838. call search('^# .*:$','W')
  839. endif
  840. else
  841. call s:StageReloadSeek(arg,line('.'),line('.'))
  842. endif
  843. return ''
  844. else
  845. return 'Git! diff --no-ext-diff '.s:shellesc(arg)
  846. endif
  847. endfunction
  848. function! s:StageToggle(lnum1,lnum2) abort
  849. if a:lnum1 == 1 && a:lnum2 == 1
  850. return 'Gedit /.git|call search("^index$", "wc")'
  851. endif
  852. try
  853. let output = ''
  854. for lnum in range(a:lnum1,a:lnum2)
  855. let [filename, section] = s:stage_info(lnum)
  856. let repo = s:repo()
  857. if getline('.') =~# '^# .*:$'
  858. if section ==# 'staged'
  859. call repo.git_chomp_in_tree('reset','-q')
  860. silent! edit!
  861. 1
  862. if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
  863. call search('^# .*:$','W')
  864. endif
  865. return ''
  866. elseif section ==# 'unstaged'
  867. call repo.git_chomp_in_tree('add','-u')
  868. silent! edit!
  869. 1
  870. if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
  871. call search('^# .*:$','W')
  872. endif
  873. return ''
  874. else
  875. call repo.git_chomp_in_tree('add','.')
  876. silent! edit!
  877. 1
  878. call search('^# .*:$','W')
  879. return ''
  880. endif
  881. endif
  882. if filename ==# ''
  883. continue
  884. endif
  885. execute lnum
  886. if section ==# 'staged'
  887. if filename =~ ' -> '
  888. let files_to_unstage = split(filename,' -> ')
  889. else
  890. let files_to_unstage = [filename]
  891. endif
  892. let filename = files_to_unstage[-1]
  893. let cmd = ['reset','-q','--'] + files_to_unstage
  894. elseif getline(lnum) =~# '^#\tdeleted:'
  895. let cmd = ['rm','--',filename]
  896. elseif getline(lnum) =~# '^#\tmodified:'
  897. let cmd = ['add','--',filename]
  898. else
  899. let cmd = ['add','-A','--',filename]
  900. endif
  901. if !exists('first_filename')
  902. let first_filename = filename
  903. endif
  904. let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
  905. endfor
  906. if exists('first_filename')
  907. call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
  908. endif
  909. echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
  910. catch /^fugitive:/
  911. return 'echoerr v:errmsg'
  912. endtry
  913. return 'checktime'
  914. endfunction
  915. function! s:StagePatch(lnum1,lnum2) abort
  916. let add = []
  917. let reset = []
  918. for lnum in range(a:lnum1,a:lnum2)
  919. let [filename, section] = s:stage_info(lnum)
  920. if getline('.') =~# '^# .*:$' && section ==# 'staged'
  921. return 'Git reset --patch'
  922. elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
  923. return 'Git add --patch'
  924. elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
  925. return 'Git add -N .'
  926. elseif filename ==# ''
  927. continue
  928. endif
  929. if !exists('first_filename')
  930. let first_filename = filename
  931. endif
  932. execute lnum
  933. if filename =~ ' -> '
  934. let reset += [split(filename,' -> ')[1]]
  935. elseif section ==# 'staged'
  936. let reset += [filename]
  937. elseif getline(lnum) !~# '^#\tdeleted:'
  938. let add += [filename]
  939. endif
  940. endfor
  941. try
  942. if !empty(add)
  943. execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
  944. endif
  945. if !empty(reset)
  946. execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
  947. endif
  948. if exists('first_filename')
  949. silent! edit!
  950. 1
  951. redraw
  952. call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
  953. endif
  954. catch /^fugitive:/
  955. return 'echoerr v:errmsg'
  956. endtry
  957. return 'checktime'
  958. endfunction
  959. " Section: Gcommit
  960. call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
  961. function! s:Commit(args, ...) abort
  962. let repo = a:0 ? a:1 : s:repo()
  963. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  964. let dir = getcwd()
  965. let msgfile = repo.dir('COMMIT_EDITMSG')
  966. let outfile = tempname()
  967. let errorfile = tempname()
  968. try
  969. try
  970. execute cd s:fnameescape(repo.tree())
  971. if s:winshell()
  972. let command = ''
  973. let old_editor = $GIT_EDITOR
  974. let $GIT_EDITOR = 'false'
  975. else
  976. let command = 'env GIT_EDITOR=false '
  977. endif
  978. let command .= repo.git_command('commit').' '.a:args
  979. if &shell =~# 'csh'
  980. noautocmd silent execute '!('.command.' > '.outfile.') >& '.errorfile
  981. elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
  982. noautocmd execute '!'.command.' 2> '.errorfile
  983. else
  984. noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
  985. endif
  986. let error = v:shell_error
  987. finally
  988. execute cd s:fnameescape(dir)
  989. endtry
  990. if !has('gui_running')
  991. redraw!
  992. endif
  993. if !error
  994. if filereadable(outfile)
  995. for line in readfile(outfile)
  996. echo line
  997. endfor
  998. endif
  999. return ''
  1000. else
  1001. let errors = readfile(errorfile)
  1002. let error = get(errors,-2,get(errors,-1,'!'))
  1003. if error =~# 'false''\=\.$'
  1004. let args = a:args
  1005. let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
  1006. let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
  1007. let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
  1008. let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
  1009. let args = '-F '.s:shellesc(msgfile).' '.args
  1010. if args !~# '\%(^\| \)--cleanup\>'
  1011. let args = '--cleanup=strip '.args
  1012. endif
  1013. if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
  1014. execute 'keepalt edit '.s:fnameescape(msgfile)
  1015. elseif a:args =~# '\%(^\| \)-\%(-verbose\|\w*v\)\>'
  1016. execute 'keepalt -tabedit '.s:fnameescape(msgfile)
  1017. elseif s:buffer().type() ==# 'index'
  1018. execute 'keepalt edit '.s:fnameescape(msgfile)
  1019. execute (search('^#','n')+1).'wincmd+'
  1020. setlocal nopreviewwindow
  1021. else
  1022. execute 'keepalt split '.s:fnameescape(msgfile)
  1023. endif
  1024. let b:fugitive_commit_arguments = args
  1025. setlocal bufhidden=wipe filetype=gitcommit
  1026. return '1'
  1027. elseif error ==# '!'
  1028. return s:Status()
  1029. else
  1030. call s:throw(empty(error)?join(errors, ' '):error)
  1031. endif
  1032. endif
  1033. catch /^fugitive:/
  1034. return 'echoerr v:errmsg'
  1035. finally
  1036. if exists('old_editor')
  1037. let $GIT_EDITOR = old_editor
  1038. endif
  1039. call delete(outfile)
  1040. call delete(errorfile)
  1041. call fugitive#reload_status()
  1042. endtry
  1043. endfunction
  1044. function! s:CommitComplete(A,L,P) abort
  1045. if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
  1046. 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=', '--fixup=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--squash=', '--template=', '--untracked-files', '--verbose']
  1047. return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
  1048. else
  1049. return s:repo().superglob(a:A)
  1050. endif
  1051. endfunction
  1052. function! s:FinishCommit() abort
  1053. let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
  1054. if !empty(args)
  1055. call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
  1056. return s:Commit(args, s:repo(getbufvar(+expand('<abuf>'),'git_dir')))
  1057. endif
  1058. return ''
  1059. endfunction
  1060. " Section: Gmerge, Gpull
  1061. call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
  1062. \ "execute s:Merge('merge', <bang>0, <q-args>)")
  1063. call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
  1064. \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
  1065. function! s:RevisionComplete(A, L, P) abort
  1066. return s:repo().git_chomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
  1067. \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
  1068. endfunction
  1069. function! s:RemoteComplete(A, L, P) abort
  1070. let remote = matchstr(a:L, ' \zs\S\+\ze ')
  1071. if !empty(remote)
  1072. let matches = split(s:repo().git_chomp('ls-remote', remote), "\n")
  1073. call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
  1074. call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
  1075. else
  1076. let matches = split(s:repo().git_chomp('remote'), "\n")
  1077. endif
  1078. return join(matches, "\n")
  1079. endfunction
  1080. function! fugitive#cwindow() abort
  1081. if &buftype == 'quickfix'
  1082. cwindow
  1083. else
  1084. botright cwindow
  1085. if &buftype == 'quickfix'
  1086. wincmd p
  1087. endif
  1088. endif
  1089. endfunction
  1090. let s:common_efm = ''
  1091. \ . '%+Egit:%.%#,'
  1092. \ . '%+Eusage:%.%#,'
  1093. \ . '%+Eerror:%.%#,'
  1094. \ . '%+Efatal:%.%#,'
  1095. \ . '%-G%.%#%\e[K%.%#,'
  1096. \ . '%-G%.%#%\r%.%\+'
  1097. function! s:Merge(cmd, bang, args) abort
  1098. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  1099. let cwd = getcwd()
  1100. let [mp, efm] = [&l:mp, &l:efm]
  1101. let had_merge_msg = filereadable(s:repo().dir('MERGE_MSG'))
  1102. try
  1103. let &l:errorformat = ''
  1104. \ . '%-Gerror:%.%#false''.,'
  1105. \ . '%-G%.%# ''git commit'' %.%#,'
  1106. \ . '%+Emerge:%.%#,'
  1107. \ . s:common_efm . ','
  1108. \ . '%+ECannot %.%#: You have unstaged changes.,'
  1109. \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
  1110. \ . '%+EThere is no tracking information for the current branch.,'
  1111. \ . '%+EYou are not currently on a branch. Please specify which,'
  1112. \ . 'CONFLICT (%m): %f deleted in %.%#,'
  1113. \ . 'CONFLICT (%m): Merge conflict in %f,'
  1114. \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
  1115. \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
  1116. \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
  1117. \ . '%+ECONFLICT %.%#,'
  1118. \ . '%+EKONFLIKT %.%#,'
  1119. \ . '%+ECONFLIT %.%#,'
  1120. \ . "%+EXUNG \u0110\u1ed8T %.%#,"
  1121. \ . "%+E\u51b2\u7a81 %.%#,"
  1122. \ . 'U%\t%f'
  1123. if a:cmd =~# '^merge' && empty(a:args) &&
  1124. \ (had_merge_msg || isdirectory(s:repo().dir('rebase-apply')) ||
  1125. \ !empty(s:repo().git_chomp('diff-files', '--diff-filter=U')))
  1126. let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
  1127. else
  1128. let &l:makeprg = s:sub(s:git_command() . ' ' . a:cmd .
  1129. \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' ? '' : ' --edit') .
  1130. \ ' ' . a:args, ' *$', '')
  1131. endif
  1132. if !empty($GIT_EDITOR) || has('win32')
  1133. let old_editor = $GIT_EDITOR
  1134. let $GIT_EDITOR = 'false'
  1135. else
  1136. let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
  1137. endif
  1138. execute cd fnameescape(s:repo().tree())
  1139. silent noautocmd make!
  1140. catch /^Vim\%((\a\+)\)\=:E211/
  1141. let err = v:exception
  1142. finally
  1143. redraw!
  1144. let [&l:mp, &l:efm] = [mp, efm]
  1145. if exists('old_editor')
  1146. let $GIT_EDITOR = old_editor
  1147. endif
  1148. execute cd fnameescape(cwd)
  1149. endtry
  1150. call fugitive#reload_status()
  1151. if empty(filter(getqflist(),'v:val.valid'))
  1152. if !had_merge_msg && filereadable(s:repo().dir('MERGE_MSG'))
  1153. cclose
  1154. return 'Gcommit --no-status -n -t '.s:shellesc(s:repo().dir('MERGE_MSG'))
  1155. endif
  1156. endif
  1157. let qflist = getqflist()
  1158. let found = 0
  1159. for e in qflist
  1160. if !empty(e.bufnr)
  1161. let found = 1
  1162. let e.pattern = '^<<<<<<<'
  1163. endif
  1164. endfor
  1165. call fugitive#cwindow()
  1166. if found
  1167. call setqflist(qflist, 'r')
  1168. if !a:bang
  1169. return 'cfirst'
  1170. endif
  1171. endif
  1172. return exists('err') ? 'echoerr '.string(err) : ''
  1173. endfunction
  1174. " Section: Ggrep, Glog
  1175. if !exists('g:fugitive_summary_format')
  1176. let g:fugitive_summary_format = '%s'
  1177. endif
  1178. call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
  1179. call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
  1180. call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<line1>,<count>,<f-args>)")
  1181. call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<line1>,<count>,<f-args>)")
  1182. function! s:Grep(cmd,bang,arg) abort
  1183. let grepprg = &grepprg
  1184. let grepformat = &grepformat
  1185. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  1186. let dir = getcwd()
  1187. try
  1188. execute cd s:fnameescape(s:repo().tree())
  1189. let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n', '--no-color')
  1190. let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
  1191. exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
  1192. let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
  1193. for entry in list
  1194. if bufname(entry.bufnr) =~ ':'
  1195. let entry.filename = s:repo().translate(bufname(entry.bufnr))
  1196. unlet! entry.bufnr
  1197. let changed = 1
  1198. elseif a:arg =~# '\%(^\| \)--cached\>'
  1199. let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
  1200. unlet! entry.bufnr
  1201. let changed = 1
  1202. endif
  1203. endfor
  1204. if a:cmd =~# '^l' && exists('changed')
  1205. call setloclist(0, list, 'r')
  1206. elseif exists('changed')
  1207. call setqflist(list, 'r')
  1208. endif
  1209. if !a:bang && !empty(list)
  1210. return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
  1211. else
  1212. return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
  1213. endif
  1214. finally
  1215. let &grepprg = grepprg
  1216. let &grepformat = grepformat
  1217. execute cd s:fnameescape(dir)
  1218. endtry
  1219. endfunction
  1220. function! s:Log(cmd, line1, line2, ...) abort
  1221. let path = s:buffer().path('/')
  1222. if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
  1223. let path = ''
  1224. endif
  1225. let cmd = ['--no-pager', 'log', '--no-color']
  1226. let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
  1227. if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
  1228. if s:buffer().commit() =~# '\x\{40\}'
  1229. let cmd += [s:buffer().commit()]
  1230. elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
  1231. let cmd += [s:buffer().path()[5:-1]]
  1232. endif
  1233. end
  1234. let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
  1235. if path =~# '/.'
  1236. if a:line2
  1237. let cmd += ['-L', a:line1 . ',' . a:line2 . ':' . path[1:-1]]
  1238. else
  1239. let cmd += ['--', path[1:-1]]
  1240. endif
  1241. endif
  1242. let grepformat = &grepformat
  1243. let grepprg = &grepprg
  1244. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  1245. let dir = getcwd()
  1246. try
  1247. execute cd s:fnameescape(s:repo().tree())
  1248. let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
  1249. let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
  1250. exe a:cmd
  1251. finally
  1252. let &grepformat = grepformat
  1253. let &grepprg = grepprg
  1254. execute cd s:fnameescape(dir)
  1255. endtry
  1256. endfunction
  1257. " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
  1258. function! s:Edit(cmd,bang,...) abort
  1259. let buffer = s:buffer()
  1260. if a:cmd !~# 'read'
  1261. if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
  1262. if winnr('$') == 1
  1263. let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
  1264. execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
  1265. elseif winnr('#')
  1266. wincmd p
  1267. else
  1268. wincmd w
  1269. endif
  1270. if &diff
  1271. let mywinnr = winnr()
  1272. for winnr in range(winnr('$'),1,-1)
  1273. if winnr != mywinnr && getwinvar(winnr,'&diff')
  1274. execute winnr.'wincmd w'
  1275. close
  1276. if winnr('$') > 1
  1277. wincmd p
  1278. endif
  1279. endif
  1280. endfor
  1281. diffoff!
  1282. endif
  1283. endif
  1284. endif
  1285. if a:bang
  1286. let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
  1287. let args = join(arglist, ' ')
  1288. if a:cmd =~# 'read'
  1289. let git = buffer.repo().git_command()
  1290. let last = line('$')
  1291. silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
  1292. if a:cmd ==# 'read'
  1293. silent execute '1,'.last.'delete_'
  1294. endif
  1295. call fugitive#reload_status()
  1296. diffupdate
  1297. return 'redraw|echo '.string(':!'.git.' '.args)
  1298. else
  1299. let temp = resolve(tempname())
  1300. if has('win32')
  1301. let temp = fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
  1302. endif
  1303. let s:temp_files[s:cpath(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
  1304. silent execute a:cmd.' '.temp
  1305. if a:cmd =~# 'pedit'
  1306. wincmd P
  1307. endif
  1308. let echo = s:Edit('read',1,args)
  1309. silent write!
  1310. setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
  1311. if getline(1) !~# '^diff '
  1312. setlocal readonly nomodifiable
  1313. endif
  1314. if a:cmd =~# 'pedit'
  1315. wincmd p
  1316. endif
  1317. return echo
  1318. endif
  1319. return ''
  1320. endif
  1321. if a:0 && a:1 == ''
  1322. return ''
  1323. elseif a:0
  1324. let file = buffer.expand(join(a:000, ' '))
  1325. elseif expand('%') ==# ''
  1326. let file = ':'
  1327. elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
  1328. let file = buffer.path(':')
  1329. else
  1330. let file = buffer.path('/')
  1331. endif
  1332. try
  1333. let file = buffer.repo().translate(file)
  1334. catch /^fugitive:/
  1335. return 'echoerr v:errmsg'
  1336. endtry
  1337. if file !~# '^fugitive:'
  1338. let file = s:sub(file, '/$', '')
  1339. endif
  1340. if a:cmd ==# 'read'
  1341. return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
  1342. else
  1343. return a:cmd.' '.s:fnameescape(file)
  1344. endif
  1345. endfunction
  1346. function! s:EditComplete(A,L,P) abort
  1347. return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
  1348. endfunction
  1349. function! s:EditRunComplete(A,L,P) abort
  1350. if a:L =~# '^\w\+!'
  1351. return s:GitComplete(a:A,a:L,a:P)
  1352. else
  1353. return s:repo().superglob(a:A)
  1354. endif
  1355. endfunction
  1356. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)")
  1357. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)")
  1358. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)")
  1359. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)")
  1360. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)")
  1361. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
  1362. call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:EditRunComplete Gread :execute s:Edit((<count> == -1 ? '' : <count>).'read',<bang>0,<f-args>)")
  1363. " Section: Gwrite, Gwq
  1364. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
  1365. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
  1366. call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
  1367. function! s:Write(force,...) abort
  1368. if exists('b:fugitive_commit_arguments')
  1369. return 'write|bdelete'
  1370. elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
  1371. return 'wq'
  1372. elseif s:buffer().type() == 'index'
  1373. return 'Gcommit'
  1374. elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
  1375. let filename = getline(4)[6:-1]
  1376. setlocal buftype=
  1377. silent write
  1378. setlocal buftype=nowrite
  1379. if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
  1380. let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
  1381. else
  1382. let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
  1383. endif
  1384. if err !=# ''
  1385. let v:errmsg = split(err,"\n")[0]
  1386. return 'echoerr v:errmsg'
  1387. elseif a:force
  1388. return 'bdelete'
  1389. else
  1390. return 'Gedit '.fnameescape(filename)
  1391. endif
  1392. endif
  1393. let mytab = tabpagenr()
  1394. let mybufnr = bufnr('')
  1395. let path = a:0 ? join(a:000, ' ') : s:buffer().path()
  1396. if empty(path)
  1397. return 'echoerr '.string('fugitive: cannot determine file path')
  1398. endif
  1399. if path =~# '^:\d\>'
  1400. return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
  1401. endif
  1402. let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
  1403. 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) !=# ''
  1404. let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
  1405. return 'echoerr v:errmsg'
  1406. endif
  1407. let file = s:repo().translate(path)
  1408. let treebufnr = 0
  1409. for nr in range(1,bufnr('$'))
  1410. if fnamemodify(bufname(nr),':p') ==# file
  1411. let treebufnr = nr
  1412. endif
  1413. endfor
  1414. if treebufnr > 0 && treebufnr != bufnr('')
  1415. let temp = tempname()
  1416. silent execute '%write '.temp
  1417. for tab in [mytab] + range(1,tabpagenr('$'))
  1418. for winnr in range(1,tabpagewinnr(tab,'$'))
  1419. if tabpagebuflist(tab)[winnr-1] == treebufnr
  1420. execute 'tabnext '.tab
  1421. if winnr != winnr()
  1422. execute winnr.'wincmd w'
  1423. let restorewinnr = 1
  1424. endif
  1425. try
  1426. let lnum = line('.')
  1427. let last = line('$')
  1428. silent execute '$read '.temp
  1429. silent execute '1,'.last.'delete_'
  1430. silent write!
  1431. silent execute lnum
  1432. let did = 1
  1433. finally
  1434. if exists('restorewinnr')
  1435. wincmd p
  1436. endif
  1437. execute 'tabnext '.mytab
  1438. endtry
  1439. endif
  1440. endfor
  1441. endfor
  1442. if !exists('did')
  1443. call writefile(readfile(temp,'b'),file,'b')
  1444. endif
  1445. else
  1446. execute 'write! '.s:fnameescape(s:repo().translate(path))
  1447. endif
  1448. if a:force
  1449. let error = s:repo().git_chomp_in_tree('add', '--force', '--', path)
  1450. else
  1451. let error = s:repo().git_chomp_in_tree('add', '--', path)
  1452. endif
  1453. if v:shell_error
  1454. let v:errmsg = 'fugitive: '.error
  1455. return 'echoerr v:errmsg'
  1456. endif
  1457. if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
  1458. set nomodified
  1459. endif
  1460. let one = s:repo().translate(':1:'.path)
  1461. let two = s:repo().translate(':2:'.path)
  1462. let three = s:repo().translate(':3:'.path)
  1463. for nr in range(1,bufnr('$'))
  1464. let name = fnamemodify(bufname(nr), ':p')
  1465. if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
  1466. execute nr.'bdelete'
  1467. endif
  1468. endfor
  1469. unlet! restorewinnr
  1470. let zero = s:repo().translate(':0:'.path)
  1471. silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
  1472. for tab in range(1,tabpagenr('$'))
  1473. for winnr in range(1,tabpagewinnr(tab,'$'))
  1474. let bufnr = tabpagebuflist(tab)[winnr-1]
  1475. let bufname = fnamemodify(bufname(bufnr), ':p')
  1476. if bufname ==# zero && bufnr != mybufnr
  1477. execute 'tabnext '.tab
  1478. if winnr != winnr()
  1479. execute winnr.'wincmd w'
  1480. let restorewinnr = 1
  1481. endif
  1482. try
  1483. let lnum = line('.')
  1484. let last = line('$')
  1485. silent execute '$read '.s:fnameescape(file)
  1486. silent execute '1,'.last.'delete_'
  1487. silent execute lnum
  1488. set nomodified
  1489. diffupdate
  1490. finally
  1491. if exists('restorewinnr')
  1492. wincmd p
  1493. endif
  1494. execute 'tabnext '.mytab
  1495. endtry
  1496. break
  1497. endif
  1498. endfor
  1499. endfor
  1500. call fugitive#reload_status()
  1501. return 'checktime'
  1502. endfunction
  1503. function! s:Wq(force,...) abort
  1504. let bang = a:force ? '!' : ''
  1505. if exists('b:fugitive_commit_arguments')
  1506. return 'wq'.bang
  1507. endif
  1508. let result = call(s:function('s:Write'),[a:force]+a:000)
  1509. if result =~# '^\%(write\|wq\|echoerr\)'
  1510. return s:sub(result,'^write','wq')
  1511. else
  1512. return result.'|quit'.bang
  1513. endif
  1514. endfunction
  1515. augroup fugitive_commit
  1516. autocmd!
  1517. autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
  1518. augroup END
  1519. " Section: Gpush, Gfetch
  1520. call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
  1521. call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
  1522. function! s:Dispatch(bang, args)
  1523. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  1524. let cwd = getcwd()
  1525. let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
  1526. try
  1527. let b:current_compiler = 'git'
  1528. let &l:errorformat = s:common_efm
  1529. let &l:makeprg = substitute(s:git_command() . ' ' . a:args, '\s\+$', '', '')
  1530. execute cd fnameescape(s:repo().tree())
  1531. if exists(':Make') == 2
  1532. noautocmd Make
  1533. else
  1534. silent noautocmd make!
  1535. redraw!
  1536. return 'call fugitive#cwindow()'
  1537. endif
  1538. return ''
  1539. finally
  1540. let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
  1541. if empty(cc) | unlet! b:current_compiler | endif
  1542. execute cd fnameescape(cwd)
  1543. endtry
  1544. endfunction
  1545. " Section: Gdiff
  1546. call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
  1547. call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
  1548. call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
  1549. augroup fugitive_diff
  1550. autocmd!
  1551. autocmd BufWinLeave *
  1552. \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
  1553. \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
  1554. \ endif
  1555. autocmd BufWinEnter *
  1556. \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
  1557. \ call s:diffoff() |
  1558. \ endif
  1559. augroup END
  1560. function! s:can_diffoff(buf) abort
  1561. return getwinvar(bufwinnr(a:buf), '&diff') &&
  1562. \ !empty(getbufvar(a:buf, 'git_dir')) &&
  1563. \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
  1564. endfunction
  1565. function! fugitive#can_diffoff(buf) abort
  1566. return s:can_diffoff(a:buf)
  1567. endfunction
  1568. function! s:diff_modifier(count) abort
  1569. let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
  1570. if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
  1571. return 'keepalt '
  1572. elseif &diffopt =~# 'vertical'
  1573. return 'keepalt vert '
  1574. elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
  1575. return 'keepalt '
  1576. else
  1577. return 'keepalt vert '
  1578. endif
  1579. endfunction
  1580. function! s:diff_window_count() abort
  1581. let c = 0
  1582. for nr in range(1,winnr('$'))
  1583. let c += getwinvar(nr,'&diff')
  1584. endfor
  1585. return c
  1586. endfunction
  1587. function! s:diff_restore() abort
  1588. let restore = 'setlocal nodiff noscrollbind'
  1589. \ . ' scrollopt=' . &l:scrollopt
  1590. \ . (&l:wrap ? ' wrap' : ' nowrap')
  1591. \ . ' foldlevel=999'
  1592. \ . ' foldmethod=' . &l:foldmethod
  1593. \ . ' foldcolumn=' . &l:foldcolumn
  1594. \ . ' foldlevel=' . &l:foldlevel
  1595. \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
  1596. if has('cursorbind')
  1597. let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
  1598. endif
  1599. return restore
  1600. endfunction
  1601. function! s:diffthis() abort
  1602. if !&diff
  1603. let w:fugitive_diff_restore = s:diff_restore()
  1604. diffthis
  1605. endif
  1606. endfunction
  1607. function! s:diffoff() abort
  1608. if exists('w:fugitive_diff_restore')
  1609. execute w:fugitive_diff_restore
  1610. unlet w:fugitive_diff_restore
  1611. else
  1612. diffoff
  1613. endif
  1614. endfunction
  1615. function! s:diffoff_all(dir) abort
  1616. let curwin = winnr()
  1617. for nr in range(1,winnr('$'))
  1618. if getwinvar(nr,'&diff')
  1619. if nr != winnr()
  1620. execute nr.'wincmd w'
  1621. let restorewinnr = 1
  1622. endif
  1623. if exists('b:git_dir') && b:git_dir ==# a:dir
  1624. call s:diffoff()
  1625. endif
  1626. endif
  1627. endfor
  1628. execute curwin.'wincmd w'
  1629. endfunction
  1630. function! s:buffer_compare_age(commit) dict abort
  1631. let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
  1632. let my_score = get(scores,':'.self.commit(),0)
  1633. let their_score = get(scores,':'.a:commit,0)
  1634. if my_score || their_score
  1635. return my_score < their_score ? -1 : my_score != their_score
  1636. elseif self.commit() ==# a:commit
  1637. return 0
  1638. endif
  1639. let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
  1640. if base ==# self.commit()
  1641. return -1
  1642. elseif base ==# a:commit
  1643. return 1
  1644. endif
  1645. let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
  1646. let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
  1647. return my_time < their_time ? -1 : my_time != their_time
  1648. endfunction
  1649. call s:add_methods('buffer',['compare_age'])
  1650. function! s:Diff(vert,keepfocus,...) abort
  1651. let args = copy(a:000)
  1652. let post = ''
  1653. if get(args, 0) =~# '^+'
  1654. let post = remove(args, 0)[1:-1]
  1655. endif
  1656. let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
  1657. if exists(':DiffGitCached')
  1658. return 'DiffGitCached'
  1659. elseif (empty(args) || args[0] == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
  1660. let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
  1661. let nr = bufnr('')
  1662. execute 'leftabove '.vert.'split' s:fnameescape(fugitive#repo().translate(s:buffer().expand(':2')))
  1663. execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
  1664. let nr2 = bufnr('')
  1665. call s:diffthis()
  1666. wincmd p
  1667. execute 'rightbelow '.vert.'split' s:fnameescape(fugitive#repo().translate(s:buffer().expand(':3')))
  1668. execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
  1669. let nr3 = bufnr('')
  1670. call s:diffthis()
  1671. wincmd p
  1672. call s:diffthis()
  1673. execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
  1674. execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
  1675. return post
  1676. elseif len(args)
  1677. let arg = join(args, ' ')
  1678. if arg ==# ''
  1679. return post
  1680. elseif arg ==# '/'
  1681. let file = s:buffer().path('/')
  1682. elseif arg ==# ':'
  1683. let file = s:buffer().path(':0:')
  1684. elseif arg =~# '^:/.'
  1685. try
  1686. let file = s:repo().rev_parse(arg).s:buffer().path(':')
  1687. catch /^fugitive:/
  1688. return 'echoerr v:errmsg'
  1689. endtry
  1690. else
  1691. let file = s:buffer().expand(arg)
  1692. endif
  1693. if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
  1694. let file = file.s:buffer().path(':')
  1695. endif
  1696. else
  1697. let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
  1698. endif
  1699. try
  1700. let spec = s:repo().translate(file)
  1701. let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
  1702. let restore = s:diff_restore()
  1703. if exists('+cursorbind')
  1704. setlocal cursorbind
  1705. endif
  1706. let w:fugitive_diff_restore = restore
  1707. if s:buffer().compare_age(commit) < 0
  1708. execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
  1709. else
  1710. execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
  1711. endif
  1712. let &l:readonly = &l:readonly
  1713. redraw
  1714. let w:fugitive_diff_restore = restore
  1715. let winnr = winnr()
  1716. if getwinvar('#', '&diff')
  1717. wincmd p
  1718. if !a:keepfocus
  1719. call feedkeys(winnr."\<C-W>w", 'n')
  1720. endif
  1721. endif
  1722. return post
  1723. catch /^fugitive:/
  1724. return 'echoerr v:errmsg'
  1725. endtry
  1726. endfunction
  1727. " Section: Gmove, Gremove
  1728. function! s:Move(force,destination) abort
  1729. if a:destination =~# '^/'
  1730. let destination = a:destination[1:-1]
  1731. else
  1732. let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
  1733. if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
  1734. let destination = destination[strlen(s:repo().tree('')):-1]
  1735. endif
  1736. endif
  1737. if isdirectory(s:buffer().spec())
  1738. " Work around Vim parser idiosyncrasy
  1739. let discarded = s:buffer().setvar('&swapfile',0)
  1740. endif
  1741. let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
  1742. if v:shell_error
  1743. let v:errmsg = 'fugitive: '.message
  1744. return 'echoerr v:errmsg'
  1745. endif
  1746. let destination = s:repo().tree(destination)
  1747. if isdirectory(destination)
  1748. let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
  1749. endif
  1750. call fugitive#reload_status()
  1751. if s:buffer().commit() == ''
  1752. if isdirectory(destination)
  1753. return 'keepalt edit '.s:fnameescape(destination)
  1754. else
  1755. return 'keepalt saveas! '.s:fnameescape(destination)
  1756. endif
  1757. else
  1758. return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
  1759. endif
  1760. endfunction
  1761. function! s:MoveComplete(A,L,P) abort
  1762. if a:A =~ '^/'
  1763. return s:repo().superglob(a:A)
  1764. else
  1765. let matches = split(glob(a:A.'*'),"\n")
  1766. call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
  1767. return matches
  1768. endif
  1769. endfunction
  1770. function! s:Remove(after, force) abort
  1771. if s:buffer().commit() ==# ''
  1772. let cmd = ['rm']
  1773. elseif s:buffer().commit() ==# '0'
  1774. let cmd = ['rm','--cached']
  1775. else
  1776. let v:errmsg = 'fugitive: rm not supported here'
  1777. return 'echoerr v:errmsg'
  1778. endif
  1779. if a:force
  1780. let cmd += ['--force']
  1781. endif
  1782. let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
  1783. if v:shell_error
  1784. let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
  1785. return 'echoerr '.string(v:errmsg)
  1786. else
  1787. call fugitive#reload_status()
  1788. return a:after . (a:force ? '!' : '')
  1789. endif
  1790. endfunction
  1791. augroup fugitive_remove
  1792. autocmd!
  1793. autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
  1794. \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
  1795. \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
  1796. \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
  1797. \ endif
  1798. augroup END
  1799. " Section: Gblame
  1800. augroup fugitive_blame
  1801. autocmd!
  1802. autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
  1803. autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
  1804. autocmd Syntax fugitiveblame call s:BlameSyntax()
  1805. 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
  1806. autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
  1807. augroup END
  1808. function! s:linechars(pattern) abort
  1809. let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
  1810. if exists('*synconcealed') && &conceallevel > 1
  1811. for col in range(1, chars)
  1812. let chars -= synconcealed(line('.'), col)[0]
  1813. endfor
  1814. endif
  1815. return chars
  1816. endfunction
  1817. function! s:Blame(bang,line1,line2,count,args) abort
  1818. if exists('b:fugitive_blamed_bufnr')
  1819. return 'bdelete'
  1820. endif
  1821. try
  1822. if s:buffer().path() == ''
  1823. call s:throw('file or blob required')
  1824. endif
  1825. if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
  1826. call s:throw('unsupported option')
  1827. endif
  1828. call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
  1829. let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
  1830. if s:buffer().commit() =~# '\D\|..'
  1831. let cmd += [s:buffer().commit()]
  1832. else
  1833. let cmd += ['--contents', '-']
  1834. endif
  1835. let cmd += ['--', s:buffer().path()]
  1836. let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!')
  1837. try
  1838. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  1839. if !s:repo().bare()
  1840. let dir = getcwd()
  1841. execute cd s:fnameescape(s:repo().tree())
  1842. endif
  1843. if a:count
  1844. execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
  1845. else
  1846. let error = resolve(tempname())
  1847. let temp = error.'.fugitiveblame'
  1848. if &shell =~# 'csh'
  1849. silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
  1850. else
  1851. silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
  1852. endif
  1853. if exists('l:dir')
  1854. execute cd s:fnameescape(dir)
  1855. unlet dir
  1856. endif
  1857. if v:shell_error
  1858. call s:throw(join(readfile(error),"\n"))
  1859. endif
  1860. for winnr in range(winnr('$'),1,-1)
  1861. call setwinvar(winnr, '&scrollbind', 0)
  1862. if exists('+cursorbind')
  1863. call setwinvar(winnr, '&cursorbind', 0)
  1864. endif
  1865. if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
  1866. execute winbufnr(winnr).'bdelete'
  1867. endif
  1868. endfor
  1869. let bufnr = bufnr('')
  1870. let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
  1871. if exists('+cursorbind')
  1872. let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
  1873. endif
  1874. if &l:wrap
  1875. let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
  1876. endif
  1877. if &l:foldenable
  1878. let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
  1879. endif
  1880. setlocal scrollbind nowrap nofoldenable
  1881. if exists('+cursorbind')
  1882. setlocal cursorbind
  1883. endif
  1884. let top = line('w0') + &scrolloff
  1885. let current = line('.')
  1886. if has('win32')
  1887. let temp = fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
  1888. endif
  1889. let s:temp_files[s:cpath(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
  1890. exe 'keepalt leftabove vsplit '.temp
  1891. let b:fugitive_blamed_bufnr = bufnr
  1892. let w:fugitive_leave = restore
  1893. let b:fugitive_blame_arguments = join(a:args,' ')
  1894. execute top
  1895. normal! zt
  1896. execute current
  1897. if exists('+cursorbind')
  1898. setlocal cursorbind
  1899. endif
  1900. setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
  1901. if exists('+concealcursor')
  1902. setlocal concealcursor=nc conceallevel=2
  1903. endif
  1904. if exists('+relativenumber')
  1905. setlocal norelativenumber
  1906. endif
  1907. execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
  1908. nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
  1909. nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
  1910. nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
  1911. nnoremap <buffer> <silent> gq :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete<Bar>if expand("%:p") =~# "^fugitive:[\\/][\\/]"<Bar>Gedit<Bar>endif','^-1','','')<CR>
  1912. nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
  1913. nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
  1914. nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
  1915. nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
  1916. nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
  1917. nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
  1918. nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
  1919. nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
  1920. nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
  1921. nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
  1922. redraw
  1923. syncbind
  1924. endif
  1925. finally
  1926. if exists('l:dir')
  1927. execute cd s:fnameescape(dir)
  1928. endif
  1929. endtry
  1930. return ''
  1931. catch /^fugitive:/
  1932. return 'echoerr v:errmsg'
  1933. endtry
  1934. endfunction
  1935. function! s:BlameCommit(cmd) abort
  1936. let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
  1937. if cmd =~# '^echoerr'
  1938. return cmd
  1939. endif
  1940. let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
  1941. let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
  1942. if path ==# ''
  1943. let path = s:buffer(b:fugitive_blamed_bufnr).path()
  1944. endif
  1945. execute cmd
  1946. if search('^diff .* b/\M'.escape(path,'\').'$','W')
  1947. call search('^+++')
  1948. let head = line('.')
  1949. while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
  1950. let top = +matchstr(getline('.'),' +\zs\d\+')
  1951. let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
  1952. if lnum >= top && lnum <= top + len
  1953. let offset = lnum - top
  1954. if &scrolloff
  1955. +
  1956. normal! zt
  1957. else
  1958. normal! zt
  1959. +
  1960. endif
  1961. while offset > 0 && line('.') < line('$')
  1962. +
  1963. if getline('.') =~# '^[ +]'
  1964. let offset -= 1
  1965. endif
  1966. endwhile
  1967. return 'normal! zv'
  1968. endif
  1969. endwhile
  1970. execute head
  1971. normal! zt
  1972. endif
  1973. return ''
  1974. endfunction
  1975. function! s:BlameJump(suffix) abort
  1976. let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
  1977. if commit =~# '^0\+$'
  1978. let commit = ':0'
  1979. endif
  1980. let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
  1981. let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
  1982. if path ==# ''
  1983. let path = s:buffer(b:fugitive_blamed_bufnr).path()
  1984. endif
  1985. let args = b:fugitive_blame_arguments
  1986. let offset = line('.') - line('w0')
  1987. let bufnr = bufnr('%')
  1988. let winnr = bufwinnr(b:fugitive_blamed_bufnr)
  1989. if winnr > 0
  1990. exe winnr.'wincmd w'
  1991. endif
  1992. execute s:Edit('edit', 0, commit.a:suffix.':'.path)
  1993. execute lnum
  1994. if winnr > 0
  1995. exe bufnr.'bdelete'
  1996. endif
  1997. if exists(':Gblame')
  1998. execute 'Gblame '.args
  1999. execute lnum
  2000. let delta = line('.') - line('w0') - offset
  2001. if delta > 0
  2002. execute 'normal! '.delta."\<C-E>"
  2003. elseif delta < 0
  2004. execute 'normal! '.(-delta)."\<C-Y>"
  2005. endif
  2006. syncbind
  2007. endif
  2008. return ''
  2009. endfunction
  2010. let s:hash_colors = {}
  2011. function! s:BlameSyntax() abort
  2012. let b:current_syntax = 'fugitiveblame'
  2013. let conceal = has('conceal') ? ' conceal' : ''
  2014. let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
  2015. syn match FugitiveblameBoundary "^\^"
  2016. syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
  2017. syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
  2018. syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
  2019. syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
  2020. syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
  2021. exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
  2022. exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
  2023. exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
  2024. exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
  2025. syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
  2026. syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
  2027. hi def link FugitiveblameBoundary Keyword
  2028. hi def link FugitiveblameHash Identifier
  2029. hi def link FugitiveblameUncommitted Ignore
  2030. hi def link FugitiveblameTime PreProc
  2031. hi def link FugitiveblameLineNumber Number
  2032. hi def link FugitiveblameOriginalFile String
  2033. hi def link FugitiveblameOriginalLineNumber Float
  2034. hi def link FugitiveblameShort FugitiveblameDelimiter
  2035. hi def link FugitiveblameDelimiter Delimiter
  2036. hi def link FugitiveblameNotCommittedYet Comment
  2037. let seen = {}
  2038. for lnum in range(1, line('$'))
  2039. let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
  2040. if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
  2041. continue
  2042. endif
  2043. let seen[hash] = 1
  2044. if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
  2045. \ && empty(get(s:hash_colors, hash))
  2046. let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
  2047. let color = csapprox#per_component#Approximate(r, g, b)
  2048. if color == 16 && &background ==# 'dark'
  2049. let color = 8
  2050. endif
  2051. let s:hash_colors[hash] = ' ctermfg='.color
  2052. else
  2053. let s:hash_colors[hash] = ''
  2054. endif
  2055. exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
  2056. endfor
  2057. call s:RehighlightBlame()
  2058. endfunction
  2059. function! s:RehighlightBlame() abort
  2060. for [hash, cterm] in items(s:hash_colors)
  2061. if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
  2062. exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
  2063. else
  2064. exe 'hi link FugitiveblameHash'.hash.' Identifier'
  2065. endif
  2066. endfor
  2067. endfunction
  2068. " Section: Gbrowse
  2069. call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
  2070. let s:redirects = {}
  2071. function! s:Browse(bang,line1,count,...) abort
  2072. try
  2073. let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
  2074. if a:0
  2075. let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
  2076. let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
  2077. else
  2078. let remote = ''
  2079. let rev = ''
  2080. endif
  2081. if rev ==# ''
  2082. let expanded = s:buffer().rev()
  2083. elseif rev ==# ':'
  2084. let expanded = s:buffer().path('/')
  2085. else
  2086. let expanded = s:buffer().expand(rev)
  2087. endif
  2088. let full = s:repo().translate(expanded)
  2089. let commit = ''
  2090. if full =~# '^fugitive://'
  2091. let commit = matchstr(full,'://.*//\zs\w\w\+')
  2092. let path = matchstr(full,'://.*//\w\+\zs/.*')
  2093. if commit =~ '..'
  2094. let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
  2095. let branch = matchstr(expanded, '^[^:]*')
  2096. else
  2097. let type = 'blob'
  2098. endif
  2099. let path = path[1:-1]
  2100. elseif s:repo().bare()
  2101. let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
  2102. let type = ''
  2103. else
  2104. let path = full[strlen(s:repo().tree())+1:-1]
  2105. if path =~# '^\.git/'
  2106. let type = ''
  2107. elseif isdirectory(full)
  2108. let type = 'tree'
  2109. else
  2110. let type = 'blob'
  2111. endif
  2112. endif
  2113. if type ==# 'tree' && !empty(path)
  2114. let path = s:sub(path, '/\=$', '/')
  2115. endif
  2116. if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
  2117. let body = readfile(s:repo().dir(path[5:-1]))[0]
  2118. if body =~# '^\x\{40\}$'
  2119. let commit = body
  2120. let type = 'commit'
  2121. let path = ''
  2122. elseif body =~# '^ref: refs/'
  2123. let path = '.git/' . matchstr(body,'ref: \zs.*')
  2124. endif
  2125. endif
  2126. let merge = ''
  2127. if path =~# '^\.git/refs/remotes/.'
  2128. if empty(remote)
  2129. let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
  2130. let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
  2131. else
  2132. let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
  2133. let path = '.git/refs/heads/'.merge
  2134. endif
  2135. elseif path =~# '^\.git/refs/heads/.'
  2136. let branch = path[16:-1]
  2137. elseif !exists('branch')
  2138. let branch = s:repo().head()
  2139. endif
  2140. if !empty(branch)
  2141. let r = s:repo().git_chomp('config','branch.'.branch.'.remote')
  2142. let m = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1]
  2143. if r ==# '.' && !empty(m)
  2144. let r2 = s:repo().git_chomp('config','branch.'.m.'.remote')
  2145. if r2 !~# '^\.\=$'
  2146. let r = r2
  2147. let m = s:repo().git_chomp('config','branch.'.m.'.merge')[11:-1]
  2148. endif
  2149. endif
  2150. if empty(remote)
  2151. let remote = r
  2152. endif
  2153. if r ==# '.' || r ==# remote
  2154. let merge = m
  2155. if path =~# '^\.git/refs/heads/.'
  2156. let path = '.git/refs/heads/'.merge
  2157. endif
  2158. endif
  2159. endif
  2160. if empty(commit) && path !~# '^\.git/'
  2161. if a:line1 && !a:count && !empty(merge)
  2162. let commit = merge
  2163. else
  2164. let commit = s:repo().rev_parse('HEAD')
  2165. endif
  2166. endif
  2167. if empty(remote)
  2168. let remote = '.'
  2169. let remote_for_url = 'origin'
  2170. else
  2171. let remote_for_url = remote
  2172. endif
  2173. if fugitive#git_version() =~# '^[01]\.\|^2\.[0-6]\.'
  2174. let raw = s:repo().git_chomp('config','remote.'.remote_for_url.'.url')
  2175. else
  2176. let raw = s:repo().git_chomp('remote','get-url',remote_for_url)
  2177. endif
  2178. if raw ==# ''
  2179. let raw = remote
  2180. endif
  2181. if raw =~# '^https\=://' && s:executable('curl')
  2182. if !has_key(s:redirects, raw)
  2183. let s:redirects[raw] = matchstr(system('curl -I ' .
  2184. \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
  2185. \ 'Location: \zs\S\+\ze/info/refs?')
  2186. endif
  2187. if len(s:redirects[raw])
  2188. let raw = s:redirects[raw]
  2189. endif
  2190. endif
  2191. for Handler in g:fugitive_browse_handlers
  2192. let url = call(Handler, [{
  2193. \ 'repo': s:repo(),
  2194. \ 'remote': raw,
  2195. \ 'revision': 'No longer provided',
  2196. \ 'commit': commit,
  2197. \ 'path': path,
  2198. \ 'type': type,
  2199. \ 'line1': a:count > 0 ? a:line1 : 0,
  2200. \ 'line2': a:count > 0 ? a:count : 0}])
  2201. if !empty(url)
  2202. break
  2203. endif
  2204. endfor
  2205. if empty(url) && raw ==# '.'
  2206. call s:throw("Instaweb failed to start")
  2207. elseif empty(url)
  2208. call s:throw("'".remote."' is not a supported remote")
  2209. endif
  2210. let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
  2211. if a:bang
  2212. if has('clipboard')
  2213. let @+ = url
  2214. endif
  2215. return 'echomsg '.string(url)
  2216. elseif exists(':Browse') == 2
  2217. return 'echomsg '.string(url).'|Browse '.url
  2218. else
  2219. if !exists('g:loaded_netrw')
  2220. runtime! autoload/netrw.vim
  2221. endif
  2222. if exists('*netrw#BrowseX')
  2223. return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
  2224. else
  2225. return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
  2226. endif
  2227. endif
  2228. catch /^fugitive:/
  2229. return 'echoerr v:errmsg'
  2230. endtry
  2231. endfunction
  2232. function! s:github_url(opts, ...) abort
  2233. if a:0 || type(a:opts) != type({})
  2234. return ''
  2235. endif
  2236. let domain_pattern = 'github\.com'
  2237. let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
  2238. for domain in domains
  2239. let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
  2240. endfor
  2241. let repo = matchstr(get(a:opts, 'remote'), '^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
  2242. if repo ==# ''
  2243. return ''
  2244. endif
  2245. call s:warn('Install rhubarb.vim for GitHub support')
  2246. return 'https://github.com/tpope/vim-rhubarb'
  2247. endfunction
  2248. function! s:instaweb_url(opts) abort
  2249. if a:opts.remote !=# '.'
  2250. return ''
  2251. endif
  2252. let output = a:opts.repo.git_chomp('instaweb','-b','unknown')
  2253. if output =~# 'http://'
  2254. let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:opts.repo.dir(),':t')
  2255. else
  2256. return ''
  2257. endif
  2258. if a:opts.path =~# '^\.git/refs/.'
  2259. return root . ';a=shortlog;h=' . matchstr(a:opts.path,'^\.git/\zs.*')
  2260. elseif a:opts.path =~# '^\.git\>'
  2261. return root
  2262. endif
  2263. let url = root
  2264. if a:opts.commit =~# '^\x\{40\}$'
  2265. if a:opts.type ==# 'commit'
  2266. let url .= ';a=commit'
  2267. endif
  2268. let url .= ';h=' . a:opts.repo.rev_parse(a:opts.commit . (a:opts.path == '' ? '' : ':' . a:opts.path))
  2269. else
  2270. if a:opts.type ==# 'blob' && empty(a:opts.commit)
  2271. let url .= ';h='.a:opts.repo.git_chomp('hash-object', '-w', a:opts.path)
  2272. else
  2273. try
  2274. let url .= ';h=' . a:opts.repo.rev_parse((a:opts.commit == '' ? 'HEAD' : ':' . a:opts.commit) . ':' . a:opts.path)
  2275. catch /^fugitive:/
  2276. call s:throw('fugitive: cannot browse uncommitted file')
  2277. endtry
  2278. endif
  2279. let root .= ';hb=' . matchstr(a:opts.repo.head_ref(),'[^ ]\+$')
  2280. endif
  2281. if a:opts.path !=# ''
  2282. let url .= ';f=' . a:opts.path
  2283. endif
  2284. if get(a:opts, 'line1')
  2285. let url .= '#l' . a:opts.line1
  2286. endif
  2287. return url
  2288. endfunction
  2289. if !exists('g:fugitive_browse_handlers')
  2290. let g:fugitive_browse_handlers = []
  2291. endif
  2292. call extend(g:fugitive_browse_handlers,
  2293. \ [s:function('s:github_url'), s:function('s:instaweb_url')])
  2294. " Section: File access
  2295. function! s:ReplaceCmd(cmd,...) abort
  2296. let fn = expand('%:p')
  2297. let tmp = tempname()
  2298. let prefix = ''
  2299. try
  2300. if a:0 && a:1 != ''
  2301. if s:winshell()
  2302. let old_index = $GIT_INDEX_FILE
  2303. let $GIT_INDEX_FILE = a:1
  2304. else
  2305. let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
  2306. endif
  2307. endif
  2308. let redir = ' > '.tmp
  2309. if &shellpipe =~ '2>&1'
  2310. let redir .= ' 2>&1'
  2311. endif
  2312. if s:winshell()
  2313. let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
  2314. call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').redir.'"')
  2315. elseif &shell =~# 'fish'
  2316. call system(' begin;'.prefix.a:cmd.redir.';end ')
  2317. else
  2318. call system(' ('.prefix.a:cmd.redir.') ')
  2319. endif
  2320. finally
  2321. if exists('old_index')
  2322. let $GIT_INDEX_FILE = old_index
  2323. endif
  2324. endtry
  2325. silent exe 'keepalt file '.tmp
  2326. try
  2327. silent edit!
  2328. finally
  2329. try
  2330. silent exe 'keepalt file '.s:fnameescape(fn)
  2331. catch /^Vim\%((\a\+)\)\=:E302/
  2332. endtry
  2333. call delete(tmp)
  2334. if fnamemodify(bufname('$'), ':p') ==# tmp
  2335. silent execute 'bwipeout '.bufnr('$')
  2336. endif
  2337. silent exe 'doau BufReadPost '.s:fnameescape(fn)
  2338. endtry
  2339. endfunction
  2340. function! s:BufReadIndex() abort
  2341. if !exists('b:fugitive_display_format')
  2342. let b:fugitive_display_format = filereadable(expand('%').'.lock')
  2343. endif
  2344. let b:fugitive_display_format = b:fugitive_display_format % 2
  2345. let b:fugitive_type = 'index'
  2346. try
  2347. let b:git_dir = s:repo().dir()
  2348. setlocal noro ma nomodeline
  2349. if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
  2350. let index = ''
  2351. else
  2352. let index = expand('%:p')
  2353. endif
  2354. if b:fugitive_display_format
  2355. call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
  2356. set ft=git nospell
  2357. else
  2358. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
  2359. let dir = getcwd()
  2360. if fugitive#git_version() =~# '^0\|^1\.[1-7]\.'
  2361. let cmd = s:repo().git_command('status')
  2362. else
  2363. let cmd = s:repo().git_command(
  2364. \ '-c', 'status.displayCommentPrefix=true',
  2365. \ '-c', 'color.status=false',
  2366. \ '-c', 'status.short=false',
  2367. \ 'status')
  2368. endif
  2369. try
  2370. execute cd s:fnameescape(s:repo().tree())
  2371. call s:ReplaceCmd(cmd, index)
  2372. finally
  2373. execute cd s:fnameescape(dir)
  2374. endtry
  2375. set ft=gitcommit
  2376. set foldtext=fugitive#foldtext()
  2377. endif
  2378. setlocal ro noma nomod noswapfile
  2379. if &bufhidden ==# ''
  2380. setlocal bufhidden=delete
  2381. endif
  2382. call s:JumpInit()
  2383. nunmap <buffer> P
  2384. nunmap <buffer> ~
  2385. nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
  2386. nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
  2387. nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
  2388. xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
  2389. nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
  2390. nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
  2391. nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
  2392. nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
  2393. nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
  2394. nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
  2395. nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
  2396. nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
  2397. nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
  2398. nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
  2399. nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
  2400. nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
  2401. nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
  2402. nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
  2403. nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
  2404. xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
  2405. nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
  2406. xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
  2407. nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
  2408. nnoremap <buffer> <silent> r :<C-U>edit<CR>
  2409. nnoremap <buffer> <silent> R :<C-U>edit<CR>
  2410. nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
  2411. nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
  2412. nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
  2413. catch /^fugitive:/
  2414. return 'echoerr v:errmsg'
  2415. endtry
  2416. endfunction
  2417. function! s:FileRead() abort
  2418. try
  2419. let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
  2420. let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
  2421. let hash = repo.rev_parse(path)
  2422. if path =~ '^:'
  2423. let type = 'blob'
  2424. else
  2425. let type = repo.git_chomp('cat-file','-t',hash)
  2426. endif
  2427. " TODO: use count, if possible
  2428. return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
  2429. catch /^fugitive:/
  2430. return 'echoerr v:errmsg'
  2431. endtry
  2432. endfunction
  2433. function! s:BufReadIndexFile() abort
  2434. try
  2435. let b:fugitive_type = 'blob'
  2436. let b:git_dir = s:repo().dir()
  2437. try
  2438. call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
  2439. finally
  2440. if &bufhidden ==# ''
  2441. setlocal bufhidden=delete
  2442. endif
  2443. setlocal noswapfile
  2444. endtry
  2445. return ''
  2446. catch /^fugitive: rev-parse/
  2447. silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
  2448. return ''
  2449. catch /^fugitive:/
  2450. return 'echoerr v:errmsg'
  2451. endtry
  2452. endfunction
  2453. function! s:BufWriteIndexFile() abort
  2454. let tmp = tempname()
  2455. try
  2456. let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
  2457. let stage = matchstr(expand('<amatch>'),'//\zs\d')
  2458. silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
  2459. let sha1 = readfile(tmp)[0]
  2460. let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
  2461. if old_mode == ''
  2462. let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
  2463. endif
  2464. let info = old_mode.' '.sha1.' '.stage."\t".path
  2465. call writefile([info],tmp)
  2466. if s:winshell()
  2467. let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
  2468. else
  2469. let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
  2470. endif
  2471. if v:shell_error == 0
  2472. setlocal nomodified
  2473. if exists('#BufWritePost')
  2474. execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
  2475. endif
  2476. call fugitive#reload_status()
  2477. return ''
  2478. else
  2479. return 'echoerr '.string('fugitive: '.error)
  2480. endif
  2481. finally
  2482. call delete(tmp)
  2483. endtry
  2484. endfunction
  2485. function! s:BufReadObject() abort
  2486. try
  2487. setlocal noro ma
  2488. let b:git_dir = s:repo().dir()
  2489. let hash = s:buffer().sha1()
  2490. if !exists("b:fugitive_type")
  2491. let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
  2492. endif
  2493. if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
  2494. return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
  2495. endif
  2496. let firstline = getline('.')
  2497. if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
  2498. let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
  2499. endif
  2500. if b:fugitive_type !=# 'blob'
  2501. setlocal nomodeline
  2502. endif
  2503. let pos = getpos('.')
  2504. silent keepjumps %delete_
  2505. setlocal endofline
  2506. try
  2507. if b:fugitive_type ==# 'tree'
  2508. let b:fugitive_display_format = b:fugitive_display_format % 2
  2509. if b:fugitive_display_format
  2510. call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
  2511. else
  2512. call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
  2513. endif
  2514. elseif b:fugitive_type ==# 'tag'
  2515. let b:fugitive_display_format = b:fugitive_display_format % 2
  2516. if b:fugitive_display_format
  2517. call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
  2518. else
  2519. call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
  2520. endif
  2521. elseif b:fugitive_type ==# 'commit'
  2522. let b:fugitive_display_format = b:fugitive_display_format % 2
  2523. if b:fugitive_display_format
  2524. call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
  2525. else
  2526. call s:ReplaceCmd(s:repo().git_command('show','--no-color','--pretty=format:tree%x20%T%nparent%x20%P%nauthor%x20%an%x20<%ae>%x20%ad%ncommitter%x20%cn%x20<%ce>%x20%cd%nencoding%x20%e%n%n%s%n%n%b',hash))
  2527. keepjumps call search('^parent ')
  2528. if getline('.') ==# 'parent '
  2529. silent keepjumps delete_
  2530. else
  2531. silent keepjumps s/\%(^parent\)\@<! /\rparent /ge
  2532. endif
  2533. keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
  2534. if lnum
  2535. silent keepjumps delete_
  2536. end
  2537. keepjumps 1
  2538. endif
  2539. elseif b:fugitive_type ==# 'blob'
  2540. call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
  2541. setlocal nomodeline
  2542. endif
  2543. finally
  2544. keepjumps call setpos('.',pos)
  2545. setlocal ro noma nomod noswapfile
  2546. if &bufhidden ==# ''
  2547. setlocal bufhidden=delete
  2548. endif
  2549. if b:fugitive_type !=# 'blob'
  2550. setlocal filetype=git foldmethod=syntax
  2551. nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
  2552. nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
  2553. else
  2554. call s:JumpInit()
  2555. endif
  2556. endtry
  2557. return ''
  2558. catch /^fugitive:/
  2559. return 'echoerr v:errmsg'
  2560. endtry
  2561. endfunction
  2562. augroup fugitive_files
  2563. autocmd!
  2564. autocmd BufReadCmd index{,.lock}
  2565. \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
  2566. \ exe s:BufReadIndex() |
  2567. \ elseif filereadable(expand('<amatch>')) |
  2568. \ read <amatch> |
  2569. \ 1delete |
  2570. \ endif
  2571. autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
  2572. autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
  2573. autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
  2574. autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
  2575. autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
  2576. autocmd FileType git
  2577. \ if exists('b:git_dir') |
  2578. \ call s:JumpInit() |
  2579. \ endif
  2580. autocmd FileType git,gitcommit,gitrebase
  2581. \ if exists('b:git_dir') |
  2582. \ call s:GFInit() |
  2583. \ endif
  2584. augroup END
  2585. " Section: Temp files
  2586. if !exists('s:temp_files')
  2587. let s:temp_files = {}
  2588. endif
  2589. augroup fugitive_temp
  2590. autocmd!
  2591. autocmd BufNewFile,BufReadPost *
  2592. \ if has_key(s:temp_files,s:cpath(expand('<afile>:p'))) |
  2593. \ let b:git_dir = s:temp_files[s:cpath(expand('<afile>:p'))].dir |
  2594. \ let b:git_type = 'temp' |
  2595. \ let b:git_args = s:temp_files[s:cpath(expand('<afile>:p'))].args |
  2596. \ call fugitive#detect(expand('<afile>:p')) |
  2597. \ setlocal bufhidden=delete nobuflisted |
  2598. \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
  2599. \ endif
  2600. augroup END
  2601. " Section: Go to file
  2602. nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
  2603. function! s:GFInit(...) abort
  2604. cnoremap <buffer> <expr> <Plug><cfile> fugitive#cfile()
  2605. if !exists('g:fugitive_no_maps') && empty(mapcheck('gf', 'n'))
  2606. nmap <buffer> <silent> gf <SID>:find <Plug><cfile><CR>
  2607. nmap <buffer> <silent> <C-W>f <SID>:sfind <Plug><cfile><CR>
  2608. nmap <buffer> <silent> <C-W><C-F> <SID>:sfind <Plug><cfile><CR>
  2609. nmap <buffer> <silent> <C-W>gf <SID>:tabfind <Plug><cfile><CR>
  2610. endif
  2611. endfunction
  2612. function! s:JumpInit(...) abort
  2613. nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
  2614. if !&modifiable
  2615. nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
  2616. nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
  2617. nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
  2618. nnoremap <buffer> <silent> - :<C-U>exe <SID>Edit('edit',0,<SID>buffer().up(v:count1))<Bar> if fugitive#buffer().type('tree')<Bar>call search('^'.escape(expand('#:t'),'.*[]~\').'/\=$','wc')<Bar>endif<CR>
  2619. nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
  2620. nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
  2621. nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
  2622. nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
  2623. nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
  2624. nnoremap <buffer> <silent> cS :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
  2625. nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
  2626. nnoremap <buffer> <silent> cP :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
  2627. nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
  2628. endif
  2629. endfunction
  2630. function! s:cfile() abort
  2631. try
  2632. let buffer = s:buffer()
  2633. let myhash = buffer.sha1()
  2634. if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
  2635. let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
  2636. endif
  2637. if buffer.type('tree')
  2638. let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
  2639. if showtree && line('.') > 2
  2640. return [buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$','')]
  2641. elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
  2642. return [buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
  2643. endif
  2644. elseif buffer.type('blob')
  2645. let ref = expand("<cfile>")
  2646. try
  2647. let sha1 = buffer.repo().rev_parse(ref)
  2648. catch /^fugitive:/
  2649. endtry
  2650. if exists('sha1')
  2651. return [ref]
  2652. endif
  2653. else
  2654. let dcmds = []
  2655. " Index
  2656. if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
  2657. let ref = matchstr(getline('.'),'\x\{40\}')
  2658. let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
  2659. return [file]
  2660. elseif getline('.') =~# '^#\trenamed:.* -> '
  2661. let file = '/'.matchstr(getline('.'),' -> \zs.*')
  2662. return [file]
  2663. elseif getline('.') =~# '^#\t\(\k\| \)\+\p\?: *.'
  2664. let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
  2665. return [file]
  2666. elseif getline('.') =~# '^#\t.'
  2667. let file = '/'.matchstr(getline('.'),'#\t\zs.*')
  2668. return [file]
  2669. elseif getline('.') =~# ': needs merge$'
  2670. let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
  2671. return [file, 'Gdiff!']
  2672. elseif getline('.') ==# '# Not currently on any branch.'
  2673. return ['HEAD']
  2674. elseif getline('.') =~# '^# On branch '
  2675. let file = 'refs/heads/'.getline('.')[12:]
  2676. return [file]
  2677. elseif getline('.') =~# "^# Your branch .*'"
  2678. let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
  2679. return [file]
  2680. endif
  2681. let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
  2682. if getline('.') =~# '^ref: '
  2683. let ref = strpart(getline('.'),5)
  2684. elseif getline('.') =~# '^commit \x\{40\}\>'
  2685. let ref = matchstr(getline('.'),'\x\{40\}')
  2686. return [ref]
  2687. elseif getline('.') =~# '^parent \x\{40\}\>'
  2688. let ref = matchstr(getline('.'),'\x\{40\}')
  2689. let line = line('.')
  2690. let parent = 0
  2691. while getline(line) =~# '^parent '
  2692. let parent += 1
  2693. let line -= 1
  2694. endwhile
  2695. return [ref]
  2696. elseif getline('.') =~ '^tree \x\{40\}$'
  2697. let ref = matchstr(getline('.'),'\x\{40\}')
  2698. if s:repo().rev_parse(myhash.':') == ref
  2699. let ref = myhash.':'
  2700. endif
  2701. return [ref]
  2702. elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
  2703. let ref = matchstr(getline('.'),'\x\{40\}')
  2704. let type = matchstr(getline(line('.')+1),'type \zs.*')
  2705. elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
  2706. let ref = buffer.rev()
  2707. elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
  2708. let ref = matchstr(getline('.'),'\x\{40\}')
  2709. echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
  2710. elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
  2711. let ref = getline('.')[4:]
  2712. elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
  2713. let type = getline('.')[0]
  2714. let lnum = line('.') - 1
  2715. let offset = 0
  2716. while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
  2717. if getline(lnum) =~# '^[ '.type.']'
  2718. let offset += 1
  2719. endif
  2720. let lnum -= 1
  2721. endwhile
  2722. let offset += matchstr(getline(lnum), type.'\zs\d\+')
  2723. let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
  2724. let dcmds = [offset, 'normal!zv']
  2725. elseif getline('.') =~# '^rename from '
  2726. let ref = 'a/'.getline('.')[12:]
  2727. elseif getline('.') =~# '^rename to '
  2728. let ref = 'b/'.getline('.')[10:]
  2729. elseif getline('.') =~# '^@@ -\d\+,\d\+ +\d\+,'
  2730. let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
  2731. let offset = matchstr(getline('.'), '+\zs\d\+')
  2732. let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
  2733. let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
  2734. let dcmd = 'Gdiff! +'.offset
  2735. elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
  2736. let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
  2737. let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
  2738. let dcmd = 'Gdiff!'
  2739. elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
  2740. let line = getline(line('.')-1)
  2741. let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
  2742. let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
  2743. let dcmd = 'Gdiff!'
  2744. elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
  2745. let ref = getline('.')
  2746. elseif expand('<cword>') =~# '^\x\{7,40\}\>'
  2747. return [expand('<cword>')]
  2748. else
  2749. let ref = ''
  2750. endif
  2751. let prefixes = {
  2752. \ '1': '',
  2753. \ '2': '',
  2754. \ 'b': ':0:',
  2755. \ 'i': ':0:',
  2756. \ 'o': '',
  2757. \ 'w': ''}
  2758. if len(myhash)
  2759. let prefixes.a = myhash.'^:'
  2760. let prefixes.b = myhash.':'
  2761. endif
  2762. let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
  2763. if exists('dref')
  2764. let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
  2765. endif
  2766. if ref ==# '/dev/null'
  2767. " Empty blob
  2768. let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
  2769. endif
  2770. if exists('dref')
  2771. return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
  2772. elseif ref != ""
  2773. return [ref] + dcmds
  2774. endif
  2775. endif
  2776. return []
  2777. endtry
  2778. endfunction
  2779. function! s:GF(mode) abort
  2780. try
  2781. let results = s:cfile()
  2782. catch /^fugitive:/
  2783. return 'echoerr v:errmsg'
  2784. endtry
  2785. if len(results)
  2786. return s:Edit(a:mode, 0, results[0]).join(map(results[1:-1], '"|".v:val'), '')
  2787. else
  2788. return ''
  2789. endif
  2790. endfunction
  2791. function! fugitive#cfile() abort
  2792. let pre = ''
  2793. let results = s:cfile()
  2794. if empty(results)
  2795. let cfile = expand('<cfile>')
  2796. if &includeexpr =~# '\<v:fname\>'
  2797. sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
  2798. endif
  2799. return cfile
  2800. elseif len(results) > 1
  2801. let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
  2802. endif
  2803. return pre . s:fnameescape(fugitive#repo().translate(results[0]))
  2804. endfunction
  2805. " Section: Statusline
  2806. function! s:repo_head_ref() dict abort
  2807. if !filereadable(self.dir('HEAD'))
  2808. return ''
  2809. endif
  2810. return readfile(self.dir('HEAD'))[0]
  2811. endfunction
  2812. call s:add_methods('repo',['head_ref'])
  2813. function! fugitive#statusline(...) abort
  2814. if !exists('b:git_dir')
  2815. return ''
  2816. endif
  2817. let status = ''
  2818. if s:buffer().commit() != ''
  2819. let status .= ':' . s:buffer().commit()[0:7]
  2820. endif
  2821. let status .= '('.fugitive#head(7).')'
  2822. if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
  2823. return ',GIT'.status
  2824. else
  2825. return '[Git'.status.']'
  2826. endif
  2827. endfunction
  2828. function! fugitive#head(...) abort
  2829. if !exists('b:git_dir')
  2830. return ''
  2831. endif
  2832. return s:repo().head(a:0 ? a:1 : 0)
  2833. endfunction
  2834. augroup fugitive_statusline
  2835. autocmd!
  2836. autocmd User Flags call Hoist('buffer', function('fugitive#statusline'))
  2837. augroup END
  2838. " Section: Folding
  2839. function! fugitive#foldtext() abort
  2840. if &foldmethod !=# 'syntax'
  2841. return foldtext()
  2842. elseif getline(v:foldstart) =~# '^diff '
  2843. let [add, remove] = [-1, -1]
  2844. let filename = ''
  2845. for lnum in range(v:foldstart, v:foldend)
  2846. if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
  2847. let filename = getline(lnum)[6:-1]
  2848. endif
  2849. if getline(lnum) =~# '^+'
  2850. let add += 1
  2851. elseif getline(lnum) =~# '^-'
  2852. let remove += 1
  2853. elseif getline(lnum) =~# '^Binary '
  2854. let binary = 1
  2855. endif
  2856. endfor
  2857. if filename ==# ''
  2858. let filename = matchstr(getline(v:foldstart), '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
  2859. endif
  2860. if filename ==# ''
  2861. let filename = getline(v:foldstart)[5:-1]
  2862. endif
  2863. if exists('binary')
  2864. return 'Binary: '.filename
  2865. else
  2866. return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
  2867. endif
  2868. elseif getline(v:foldstart) =~# '^# .*:$'
  2869. let lines = getline(v:foldstart, v:foldend)
  2870. call filter(lines, 'v:val =~# "^#\t"')
  2871. cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
  2872. cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
  2873. return getline(v:foldstart).' '.join(lines, ', ')
  2874. endif
  2875. return foldtext()
  2876. endfunction
  2877. augroup fugitive_foldtext
  2878. autocmd!
  2879. autocmd User Fugitive
  2880. \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
  2881. \ set foldtext=fugitive#foldtext() |
  2882. \ endif
  2883. augroup END