Allow for branch names with slashs in them.
[vim-git-branch-info.git] / git-branch-info.vim
blob3b40dcde581b48522017c6ec605c8457a4948866
2 " Git branch info
3 " Last change: June 01 2009
4 " Version> 0.1.3
5 " Maintainer: Eustáquio 'TaQ' Rangel
6 " License: GPL
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 
27 " like:
29 " let g:git_branch_status_head_current=1
30 " This will show just the current head branch name 
31
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
42 " end.
43
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 " let g:git_branch_check_write=<something>
49 " Check the current branch if it's the same branch where the file was loaded, 
50 " before saving the file.
52 " If you want to make your own customizations, you can use the GitBranchInfoTokens()
53 " function. It returns an array with the current branch as the first element and
54 " another array with the other branches as the second element, like:
56 " :set statusline=%#ErrorMsg#%{GitBranchInfoTokens()[0]}%#StatusLine#
58 " or
60 " :set statusline=%#StatusLineNC#\ Git\ %#ErrorMsg#\ %{GitBranchInfoTokens()[0]}\ %#StatusLine#
62 " will give you a nice custom formatted string.
64 " This will show you the current branch only. No prefix text, no characters
65 " around it. You can also make another functions to use the returned array.
67 let s:menu_on           = 0
68 let s:checking          = ""
69 let s:rebase_msg        = 'Rebasing,merging,bisecting?'
70 let b:git_dir   = ""
71 let b:git_load_branch = ""
73 autocmd BufEnter * call GitBranchInfoInit()
75 if exists("g:git_branch_check_write")
76         autocmd BufWriteCmd * call GitBranchInfoWriteCheck()
77 endif   
79 function GitBranchInfoCheckGitDir()
80         return exists("b:git_dir") && !empty(b:git_dir)
81 endfunction
83 function GitBranchInfoCheckReadable()
84         return filereadable(b:git_dir."/HEAD")
85 endfunction
87 function GitBranchInfoWriteCheck()
88         let l:writecmd = v:cmdbang==1 ? "write!" : "write"
89         " not controlled by Git, write this thing!
90         if !GitBranchInfoCheckGitDir()
91                 exec l:writecmd expand("<afile>")
92                 return 1
93         endif
95         " just write normal buffers
96         let l:buftype = getbufvar(bufnr("%"),'&buftype')
97         if strlen(l:buftype)>0
98                 echohl WarningMsg
99                 echo "Not writing if it's not a normal buffer (found a ".l:buftype." buffer)."
100                 echohl None
101                 return 0
102         endif
104         " if the branches are the same, no problem
105         let l:current = GitBranchInfoTokens()[0]
106         if l:current==b:git_load_branch
107                 exec l:writecmd expand("<afile>")
108                 return 1
109         endif
111         " if we're rebasing, merging or bisecting, write the file
112         if l:current==s:rebase_msg
113                 exec l:writecmd expand("<afile>")
114                 return 1
115         endif
117         " ask what we will do
118         echohl ErrorMsg
119         let l:answer = tolower(input("Loaded from \'".b:git_load_branch."\' branch but saving on \'".l:current."\' branch, confirm [y/n]? ","n"))
120         echohl None
121         let l:msg = "File ".(l:answer=="y" ? "" : "NOT ")."saved on branch \'".l:current."\'."
123         " ok, save even with different branches
124         if l:answer=="y"
125                 exec l:writecmd expand("<afile>")
126         endif
128         " show message
129         echohl WarningMsg
130         echo l:msg
131         echohl None
132         return l:answer=="y"
133 endfunction
135 function GitBranchInfoInit()
136         call GitBranchInfoFindDir()
137         let l:current = GitBranchInfoTokens()
138         let b:git_load_branch = l:current[0]
139 endfunction
141 function GitBranchInfoFindDir()
142         let l:bufname   = getcwd()."/".expand("%:t")
143         let l:buflist   = strlen(l:bufname)>0 ? split(l:bufname,"/") : [""]
144         let l:prefix    = l:bufname =~ "^/" ? "/" : ""
145         let b:git_dir   = ""
146         while len(l:buflist) > 0
147                 let l:path = l:prefix.join(l:buflist,"/").l:prefix.".git"
148                 if !empty(finddir(l:path))
149                         let b:git_dir = l:path
150                         break
151                 endif
152                 call remove(l:buflist,-1)
153         endwhile
154         return b:git_dir
155 endfunction
157 function GitBranchInfoGitDir()
158         return b:git_dir
159 endfunction
161 function GitBranchInfoLoadBranch()
162         return b:git_load_branch
163 endfunction
165 function GitBranchInfoRenewMenu(current,heads,remotes)
166         call GitBranchInfoRemoveMenu()
167         call GitBranchInfoShowMenu(a:current,a:heads,a:remotes)
168 endfunction
170 function GitBranchInfoCheckout(branch)
171         let l:tokens    = GitBranchInfoTokens()
172         let l:checkout  = "git\ checkout\ ".a:branch 
173         let l:where             = substitute(b:git_dir,".git$","","")
174         let l:cmd               = strlen(l:where)>0 ? "!cd\ ".l:where.";\ ".l:checkout : "!".l:checkout
175         exe l:cmd
176         call GitBranchInfoRenewMenu(l:tokens[0],l:tokens[1],l:tokens[2])
177 endfunction
179 function GitBranchInfoFetch(remote)
180         let l:tokens    = GitBranchInfoTokens()
181         let l:fetch             =  "git\ fetch\ ".a:remote
182         let l:where             = substitute(b:git_dir,".git$","","")
183         let l:cmd               = strlen(l:where)>0 ? "!cd\ ".l:where.";\ ".l:fetch : "!".l:fetch
184         exe l:cmd
185 endfunction
187 function GitBranchInfoShowMenu(current,heads,remotes)
188         if !has("gui")
189                 return
190         endif
191         let s:menu_on   = 1
192         let l:compare   = a:current
193         let l:current   = [a:current]
194         let l:heads             = len(a:heads)>0         ? a:heads       : []
195         let l:remotes   = len(a:remotes)>0 ? a:remotes : []
196         let l:locals    = sort(extend(l:current,l:heads))
197         for l:branch in l:locals
198                 let l:moption   = (l:branch==l:compare ? "Working\\ \\on\\ " : "Checkout\\ ").l:branch
199                 let l:mcom              = (l:branch==l:compare ? ":echo 'Already\ on\ branch\ \''".l:branch."\''.'<CR>" : "call GitBranchInfoCheckout('".l:branch."')<CR><CR>")
200                 exe ":menu <silent> Plugin.Git\\ Info.".l:moption." :".l:mcom
201         endfor
202         exe ":menu <silent> Plugin.Git\\ Info.-Local- :"
203         let l:lastone = ""
204         for l:branch in l:remotes
205                 let l:tokens    = split(l:branch,"/")
206                 if l:tokens[0]==l:lastone
207                         continue
208                 endif
209                 let l:lastone = l:tokens[0]
210                 exe "menu <silent> Plugin.Git\\ Info.Fetch\\ ".l:tokens[0]." :call GitBranchInfoFetch('".l:tokens[0]."')<CR><CR>"
211         endfor
212 endfunction
214 function GitBranchInfoRemoveMenu()
215         if !has("gui") || s:menu_on==0
216                 return
217         endif
218         exe ":unmenu Plugin.Git\\ Info" 
219         let s:menu_on = 0
220 endfunction
222 function GitBranchInfoString()
223         let l:tokens    = GitBranchInfoTokens() " get the tokens
224         if len(l:tokens)==1                                                     " no git here
225                 call GitBranchInfoRemoveMenu()
226                 return l:tokens[0]
227         end
228         let s:current   = l:tokens[0]                           " the current branch is the first one
229         let l:branches  = l:tokens[1]                           " the other branches are the last one
230         let l:remotes   = l:tokens[2]                           " remote branches
231         " check for around characters
232         let l:around    = exists("g:git_branch_status_around") ? (strlen(g:git_branch_status_around)==2 ? split(g:git_branch_status_around,'\zs') : ["",""]) : ["[","]"]
233         " find the prefix text
234         let l:text              = exists("g:git_branch_status_text")   ? g:git_branch_status_text : " Git "
235         if s:menu_on == 0
236                 call GitBranchInfoShowMenu(l:tokens[0],l:tokens[1],l:tokens[2])
237         endif
238         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])
239 endfunction
241 function GitBranchInfoTokens()
242         if !GitBranchInfoCheckGitDir()
243                 let s:current = ''
244                 return [exists("g:git_branch_status_nogit") ? g:git_branch_status_nogit : "No git."]
245         endif
246         if !GitBranchInfoCheckReadable()
247                 let s:current = ''
248                 return [s:current,[],[]]
249         endif
250         try
251                 let l:contents  = readfile(b:git_dir."/HEAD",'',1)[0]
252                 if stridx(l:contents,"/") < 0
253                         let s:current = s:rebase_msg
254                         return [s:current,[],[]]
255                 endif
256                 "TA:  2009/07/04 -- Allow for branches with slashes in their name, a
257                 "la "auth/current-branch", by only removing the refs/head/ prefix.
258                 let s:current = substitute(l:contents, "^.*refs\/heads\/", "", "")
259                 if exists("g:git_branch_status_head_current")
260                         let l:heads     = []
261                 else            
262                         let l:heads     = split(glob(b:git_dir."/refs/heads/*"),"\n")
263                         call map(l:heads,'substitute(v:val,b:git_dir."/refs/heads/","","")')
264                         call sort(filter(l:heads,'v:val !~ s:current'))
265                 endif           
266                 if exists("g:git_branch_status_ignore_remotes")
267                         let l:remotes = []
268                 else
269                         let l:remotes   = split(glob(b:git_dir."/refs/remotes/*/**"),"\n")
270                         call sort(map(l:remotes,'substitute(v:val,b:git_dir."/refs/remotes/","","")'))
271                 endif           
272                 let l:checking = s:current.join(l:heads).join(l:remotes)
273                 if l:checking != s:checking && has("gui")
274                         call GitBranchInfoRenewMenu(s:current,l:heads,l:remotes)
275                 endif
276                 let s:checking = l:checking
277         catch /.*/
278                 let s:current   = '???'
279                 let l:heads             = []
280                 let l:remotes   = []
281         endtry
282         return [s:current,l:heads,l:remotes]
283 endfunction