3 " Last change: June 26 2008
5 " Maintainer: Eustáquio 'TaQ' Rangel
7 " URL: git://github.com/taq/vim-git-branch-info.git
9 " This plugin show branches information on the status line.
10 " To install, just put this file on ~/.vim/plugins and set your status line:
12 " :set statusline=%{GitBranchInfoString}
14 " Of course you can append this configuration to an existing one and make all
15 " the customization you want on the status line, like:
17 " :set statusline=%#ErrorMsg#%{GitBranchInfoString}%#StatusLine#
19 " The command above will show the Git branches info on the same color as the
20 " error messages. You can choose any color scheme you want to. Use
22 " :help highlight-groups
24 " to check for some other options.
26 " There are some customization on the result string based on existing variables
29 " let g:git_branch_status_head_current=1
30 " This will show just the current head branch name
32 " let g:git_branch_status_text="text"
33 " This will show 'text' before the branches. If not set ' Git ' (with a trailing
34 " left space) will be displayed.
36 " let g:git_branch_status_nogit=""
37 " The message when there is no Git repository on the current dir
39 " let g:git_branch_status_around=""
40 " Characters to put around the branch strings. Need to be a pair or characters,
41 " the first will be on the beginning of the branch string and the last on the
44 " let g:git_branch_status_ignore_remotes=1
45 " Ignore the remote branches. If you don't want information about them, this can
46 " make things works faster.
48 " If you want to make your own customizations, you can use the GitBranchInfoTokens()
49 " function. It returns an array with the current branch as the first element and
50 " another array with the other branches as the second element, like:
52 " :set statusline=%#ErrorMsg#%{GitBranchInfoTokens()[0]}%#StatusLine#
56 " :set statusline=%#StatusLineNC#\ Git\ %#ErrorMsg#\ %{GitBranchInfoTokens()[0]}\ %#StatusLine#
58 " will give you a nice custom formatted string.
60 " This will show you the current branch only. No prefix text, no characters
61 " around it. You can also make another functions to use the returned array.
65 let s:rebase_msg = 'Rebasing,merging,bisecting?'
67 let b:git_load_branch = ""
69 autocmd BufEnter * call GitBranchInfoInit()
70 autocmd BufWriteCmd * call GitBranchInfoWriteCheck()
72 function GitBranchInfoCheckGitDir()
73 return exists("b:git_dir") && !empty(b:git_dir)
76 function GitBranchInfoCheckReadable()
77 return filereadable(b:git_dir."/HEAD")
80 function GitBranchInfoWriteCheck()
81 let l:writecmd = v:cmdbang==1 ? "write!" : "write"
82 " not controlled by Git, write this thing!
83 if !GitBranchInfoCheckGitDir()
84 exec l:writecmd expand("<afile>")
88 " just write normal buffers
89 let l:buftype = getbufvar(bufnr("%"),'&buftype')
90 if strlen(l:buftype)>0
92 echo "Not writing if it's not a normal buffer (found a ".l:buftype." buffer)."
97 " if the branches are the same, no problem
98 let l:current = GitBranchInfoTokens()[0]
99 if l:current==b:git_load_branch
100 exec l:writecmd expand("<afile>")
104 " if we're rebasing, merging or bisecting, write the file
105 if l:current==s:rebase_msg
106 exec l:writecmd expand("<afile>")
110 " ask what we will do
112 let l:answer = tolower(input("Loaded from \'".b:git_load_branch."\' branch but saving on \'".l:current."\' branch, confirm [y/n]? ","n"))
114 let l:msg = "File ".(l:answer=="y" ? "" : "NOT ")."saved on branch \'".l:current."\'."
116 " ok, save even with different branches
118 exec l:writecmd expand("<afile>")
128 function GitBranchInfoInit()
129 call GitBranchInfoFindDir()
130 let l:current = GitBranchInfoTokens()
131 let b:git_load_branch = l:current[0]
134 function GitBranchInfoFindDir()
135 let l:bufname = bufname("%")
136 let l:buflist = strlen(l:bufname)>0 ? split(l:bufname,"/") : [""]
137 let l:prefix = l:bufname =~ "^/" ? "/" : ""
139 for l:item in l:buflist
140 call remove(l:buflist,-1)
141 let l:path = l:prefix.join(l:buflist,"/").l:prefix.".git"
142 if !empty(finddir(l:path))
143 let b:git_dir = l:path
150 function GitBranchInfoGitDir()
154 function GitBranchInfoLoadBranch()
155 return b:git_load_branch
158 function GitBranchInfoRenewMenu(current,heads,remotes)
159 call GitBranchInfoRemoveMenu()
160 call GitBranchInfoShowMenu(a:current,a:heads,a:remotes)
163 function GitBranchInfoCheckout(branch)
164 let l:tokens = GitBranchInfoTokens()
165 let l:checkout = "git\ checkout\ ".a:branch
166 let l:where = substitute(b:git_dir,".git$","","")
167 let l:cmd = strlen(l:where)>0 ? "!cd\ ".l:where.";\ ".l:checkout : "!".l:checkout
169 call GitBranchInfoRenewMenu(l:tokens[0],l:tokens[1],l:tokens[2])
172 function GitBranchInfoFetch(remote)
173 let l:tokens = GitBranchInfoTokens()
174 let l:fetch = "git\ fetch\ ".a:remote
175 let l:where = substitute(b:git_dir,".git$","","")
176 let l:cmd = strlen(l:where)>0 ? "!cd\ ".l:where.";\ ".l:fetch : "!".l:fetch
180 function GitBranchInfoShowMenu(current,heads,remotes)
185 let l:compare = a:current
186 let l:current = [a:current]
187 let l:heads = len(a:heads)>0 ? a:heads : []
188 let l:remotes = len(a:remotes)>0 ? a:remotes : []
189 let l:locals = sort(extend(l:current,l:heads))
190 for l:branch in l:locals
191 let l:moption = (l:branch==l:compare ? "Working\\ \\on\\ " : "Checkout\\ ").l:branch
192 let l:mcom = (l:branch==l:compare ? ":echo 'Already\ on\ branch\ \''".l:branch."\''.'<CR>" : "call GitBranchInfoCheckout('".l:branch."')<CR><CR>")
193 exe ":menu <silent> Plugin.Git\\ Info.".l:moption." :".l:mcom
195 exe ":menu <silent> Plugin.Git\\ Info.-Local- :"
197 for l:branch in l:remotes
198 let l:tokens = split(l:branch,"/")
199 if l:tokens[0]==l:lastone
202 let l:lastone = l:tokens[0]
203 exe "menu <silent> Plugin.Git\\ Info.Fetch\\ ".l:tokens[0]." :call GitBranchInfoFetch('".l:tokens[0]."')<CR><CR>"
207 function GitBranchInfoRemoveMenu()
208 if !has("gui") || s:menu_on==0
211 exe ":unmenu Plugin.Git\\ Info"
215 function GitBranchInfoString()
216 let l:tokens = GitBranchInfoTokens() " get the tokens
217 if len(l:tokens)==1 " no git here
218 call GitBranchInfoRemoveMenu()
221 let s:current = l:tokens[0] " the current branch is the first one
222 let l:branches = l:tokens[1] " the other branches are the last one
223 let l:remotes = l:tokens[2] " remote branches
224 " check for around characters
225 let l:around = exists("g:git_branch_status_around") ? (strlen(g:git_branch_status_around)==2 ? split(g:git_branch_status_around,'\zs') : ["",""]) : ["[","]"]
226 " find the prefix text
227 let l:text = exists("g:git_branch_status_text") ? g:git_branch_status_text : " Git "
229 call GitBranchInfoShowMenu(l:tokens[0],l:tokens[1],l:tokens[2])
231 return l:text.l:around[0].s:current.l:around[1].(exists("g:git_branch_status_head_current")?"":l:around[0].join(l:branches,",").l:around[1])
234 function GitBranchInfoTokens()
235 if !GitBranchInfoCheckGitDir()
237 return [exists("g:git_branch_status_nogit") ? g:git_branch_status_nogit : "No git."]
239 if !GitBranchInfoCheckReadable()
241 return [s:current,[],[]]
244 let l:contents = readfile(b:git_dir."/HEAD",'',1)[0]
245 if stridx(l:contents,"/") < 0
246 let s:current = s:rebase_msg
247 return [s:current,[],[]]
249 let s:current = split(split(l:contents)[1],"/")[2]
250 if exists("g:git_branch_status_head_current")
253 let l:heads = split(glob(b:git_dir."/refs/heads/*"),"\n")
254 call map(l:heads,'substitute(v:val,b:git_dir."/refs/heads/","","")')
255 call sort(filter(l:heads,'v:val !~ s:current'))
257 if exists("g:git_branch_status_ignore_remotes")
260 let l:remotes = split(glob(b:git_dir."/refs/remotes/*/**"),"\n")
261 call sort(map(l:remotes,'substitute(v:val,b:git_dir."/refs/remotes/","","")'))
263 let l:checking = s:current.join(l:heads).join(l:remotes)
264 if l:checking != s:checking && has("gui")
265 call GitBranchInfoRenewMenu(s:current,l:heads,l:remotes)
267 let s:checking = l:checking
269 let s:current = '???'
273 return [s:current,l:heads,l:remotes]