diff --git a/README.markdown b/README.markdown index 0ae91a0..f097413 100644 --- a/README.markdown +++ b/README.markdown @@ -17,7 +17,8 @@ mofo. And guess what `:Gcommit` does! `:Gblame` brings up an interactive vertical split with `git blame` output. Press enter on a line to reblame the file as it stood in that -commit, or `o` to open that commit in a split. +commit, or `o` to open that commit in a split. When you're done, use +`:Gedit` in the historic buffer to go back to the work tree version. `:Gmove` does a `git mv` on a file and simultaneously renames the buffer. `:Gremove` does a `git rm` on a file and simultaneously deletes @@ -35,6 +36,10 @@ and you never get any warnings about the file changing outside Vim. making it like `git add` when called from a work tree file and like `git checkout` when called from the index or a blob in history. +Use `:Gbrowse` to open the current file on GitHub, with optional line +range (try it in visual mode!). If your current repository isn't on +GitHub, `git instaweb` will be spun up instead. + Add `%{fugitive#statusline()}` to `'statusline'` to get an indicator with the current branch in (surprise!) your statusline. diff --git a/doc/fugitive.txt b/doc/fugitive.txt index 8ba2e8b..beb5c1a 100644 --- a/doc/fugitive.txt +++ b/doc/fugitive.txt @@ -154,6 +154,33 @@ that are part of Git repositories). :[range]Gblame [flags] Run git-blame on the given range. + *fugitive-:Gbrowse* +:[range]Gbrowse If the remote for the current branch is on GitHub, + open the current file, blob, tree, commit, or tag + (with git-web--browse) on GitHub. Otherwise, open the + current flie, blob, tree, commit, or tag in + git-instaweb (if you have issues, verify you can run + "git instaweb" from a terminal). If a range is given, + it is appropriately appended to the URL as an anchor. + +:[range]Gbrowse! Like :Gbrowse, but put the URL on the clipboard rather + than opening it. + +:[range]Gbrowse - Like :Gbrowse, but tie the URL to the current HEAD. + This is useful in a work tree file to ensure the link + always goes to the file as it currently stands, rather + than whatever the latest on master is. Note that + when editing a historical file, said link is tied by + default to the commit you're currently browsing. (The + intent is that ultimately, one will be able to specify + any |fugitive-revision| here, but the current + implementation is limited to this one special case.) + +[range]Gbrowse [...]@{remote} + Force using the given remote rather than the remote + for the current branch. The remote is used to + determine which GitHub repository to link to. + MAPPINGS *fugitive-mappings* These maps are available in Git objects. diff --git a/plugin/fugitive.vim b/plugin/fugitive.vim index 2a01af4..3ff182e 100644 --- a/plugin/fugitive.vim +++ b/plugin/fugitive.vim @@ -900,7 +900,7 @@ call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gtabedit call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditComplete Gread :execute s:Edit((! && ? '' : ).'read',)") " }}}1 -" Gwrite {{{1 +" Gwrite, Gwq {{{1 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(0,)") @@ -1367,6 +1367,127 @@ function! s:BlameSyntax() abort hi def link FugitiveblameNotCommittedYet Comment endfunction +" }}}1 +" Gbrowse {{{1 + +call s:command("-bar -bang -count=0 -nargs=? Gbrowse :execute s:Browse(0,,,)") + +function! s:Browse(bang,line1,count,...) abort + try + let rev = a:0 ? substitute(a:1,'@[[:alnum:]_-]\+$','','') : '' + if a:0 && a:1 =~# '@[[:alnum:]_-]\+$' + let remote = matchstr(a:1,'@\zs[[:alnum:]_-]\+$') + elseif s:buffer().path() =~# '^\.git/refs/remotes/.' + let remote = matchstr(s:buffer().path(),'^\.git/refs/remotes/\zs[^/]\+') + else + let remote = 'origin' + let branch = matchstr(s:repo().head_ref(),'\' + return root + endif + if a:rev ==# '-' + let commit = self.repo().rev_parse('HEAD') + elseif self.commit() =~# '^\x\{40\}$' + let commit = self.commit() + else + let commit = matchstr(self.repo().head_ref(),'[^/]\+$') + endif + if self.type() ==# 'directory' || self.type() ==# 'tree' + let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','') + elseif self.type() ==# 'file' || self.type() ==# 'blob' + let url = root . '/blob/' . commit . '/' . path + if a:line2 && a:line1 == a:line2 + let url .= '#L' . a:line1 + elseif a:line2 + let url .= '#L' . a:line1 . '-' . a:line2 + endif + elseif self.type() ==# 'tag' + let commit = matchstr(getline(3),'^tag \zs.*') + let url = root . '/tree/' . commit + else + let url = root . '/commit/' . commit + endif + return url +endfunction + +function! s:instaweb_url(buffer,rev,...) abort + let self = a:buffer + let path = self.path() + let output = self.repo().git_chomp('instaweb','-b','unknown') + if output =~# 'http://' + let root = matchstr(output,'http://.*').'/?p='.fnamemodify(self.repo().dir(),':t') + else + return '' + endif + if path =~# '^\.git/refs/.' + return root . ';a=shortlog;h=' . matchstr(path,'^\.git/\zs.*') + elseif path =~# '^\.git\>' + return root + endif + let url = root + if self.commit() =~# '^\x\{40\}$' || a:rev ==# '-' + let ref = a:rev ==# '-' ? 'HEAD' : self.commit() + let url .= ';h=' . self.repo().rev_parse(ref . path) + else + if self.type() ==# 'file' || self.type() ==# 'blob' + let tmp = tempname() + silent execute 'write !'.self.repo().git_command('hash-object','-w','--stdin').' > '.tmp + let url .= ';h=' . readfile(tmp)[0] + else + try + let url .= ';h=' . self.repo().rev_parse(self.path((self.commit() == '' ? 'HEAD' : ':'.self.commit()).':')) + catch /^fugitive:/ + endtry + endif + let root .= ';hb=' . matchstr(self.repo().head_ref(),'[^ ]\+$') + endif + if path !=# '' + let url .= ';f=' . path + endif + if a:0 && a:1 + let url .= '#l' . a:1 + endif + return url +endfunction + " }}}1 " File access {{{1