From df36d194ce8cdb26ff0a21f4652bc19cd352d7c4 Mon Sep 17 00:00:00 2001 From: Tim Pope Date: Mon, 27 Jun 2022 01:10:49 -0400 Subject: [PATCH] Prototype "reverse :GBrowse" Use `:Gedit https://github.com/...` (or any other URL supported by an installed :GBrowse provider) to edit the corresponding `fugitive://` URL. The dictionary g:fugitive_url_origins maps between homepage URLs and local repositories: let g:fugitive_url_origins = { 'https://github.com/me/my-repo': '~/Projects/my-repo'} It also checks the remotes of the repository that the currently edited buffer belongs to. This is an experimental prototype. That means no documentation, and no guarantees about behavior. In particular, g:fugitive_url_origins and the current contortions to leverage the existing :GBrowse API will likely be dropped once a better API has been developed. References: https://github.com/tpope/vim-fugitive/issues/2223 --- autoload/fugitive.vim | 113 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/autoload/fugitive.vim b/autoload/fugitive.vim index f608b56..d5f8cc6 100644 --- a/autoload/fugitive.vim +++ b/autoload/fugitive.vim @@ -6112,6 +6112,10 @@ function! s:OpenExpand(dir, file, wants_cmd) abort else let efile = s:Expand(a:file) endif + if efile =~# '^https\=://' + let [url, lnum] = s:ResolveUrl(efile, dir) + return [url, a:wants_cmd ? lnum : 0] + endif let url = s:Generate(efile, a:dir) if a:wants_cmd && a:file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw' let line = line('.') @@ -7608,6 +7612,115 @@ function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, ...) abor endtry endfunction +function! s:RemoteRefToLocalRef(repo, remote_url, ref_path) abort + let ref_path = substitute(a:ref_path, ':', '/', '') + let rev = '' + if ref_path =~# '^\x\{40,\}\%(/\|$\)' + let rev = substitute(ref_path, '/', ':', '') + elseif ref_path =~# '^[^:/^~]\+' + let first_component = matchstr(ref_path, '^[^:/^~]\+') + let lines = fugitive#Execute(['ls-remote', a:remote_url, first_component, first_component . '/*'], a:repo).stdout[0:-2] + for line in lines + let full = matchstr(line, "\t\\zs.*") + for candidate in [full, matchstr(full, '^refs/\w\+/\zs.*')] + if candidate ==# first_component || strpart(ref_path . '/', 0, len(candidate) + 1) ==# candidate . '/' + let rev = matchstr(line, '^\x\+') . substitute(strpart(ref_path, len(candidate)), '/', ':', '') + endif + endfor + endfor + endif + if empty(rev) + return '' + endif + let commitish = matchstr(rev, '^[^:^~]*') + let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo) + if rev_parse.exit_status + if fugitive#Execute(['fetch', remote_url, commitish], a:repo).exit_status + return '' + endif + let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo) + endif + if rev_parse.exit_status + return '' + endif + return rev_parse.stdout[0] . matchstr(rev, ':.*') +endfunction + +function! fugitive#ResolveUrl(target, ...) abort + let repo = call('s:Dir', a:000) + let origins = get(g:, 'fugitive_url_origins', {}) + let prefix = substitute(s:Slash(a:target), '#.*', '', '') + while prefix =~# '://' + let extracted = FugitiveExtractGitDir(expand(get(origins, prefix, ''))) + if !empty(extracted) + let repo = s:Dir(extracted) + break + endif + let prefix = matchstr(prefix, '.*\ze/') + endwhile + let git_dir = s:GitDir(repo) + for remote_name in keys(FugitiveConfigGetRegexp('^remote\.\zs.*\ze\.url$', repo)) + let remote_url = fugitive#RemoteUrl(remote_name, repo) + for [no_anchor; variant] in [[1, 'commit'], [1, 'tree'], [1, 'tree', 1], [1, 'blob', 1], [0, 'blob', 1, '1`line1`', '1`line1`'], [0, 'blob', 1, '1`line1`', '2`line2`']] + let handler_opts = { + \ 'git_dir': git_dir, + \ 'repo': {'git_dir': git_dir}, + \ 'remote': remote_url, + \ 'remote_name': remote_name, + \ 'commit': '1`commit`', + \ 'type': get(variant, 0), + \ 'path': get(variant, 1) ? '1`path`' : '', + \ 'line1': get(variant, 2), + \ 'line2': get(variant, 3)} + let url = '' + for l:.Handler in get(g:, 'fugitive_browse_handlers', []) + let l:.url = call(Handler, [copy(handler_opts)]) + if type(url) == type('') && url =~# '://' + break + endif + endfor + if type(url) != type('') || url !~# '://' + continue + endif + let keys = split(substitute(url, '\d`\(\w\+`\)\|.', '\1', 'g'), '`') + let pattern = substitute(url, '\d`\w\+`\|[][^$.*\~]', '\=len(submatch(0)) == 1 ? "\\" . submatch(0) : "\\([^#?&;]\\{-\\}\\)"', 'g') + let pattern = '^' . substitute(pattern, '^https\=:', 'https\\=:', '') . '$' + let target = s:Slash(no_anchor ? substitute(a:target, '#.*', '', '') : a:target) + let values = matchlist(s:Slash(a:target), pattern)[1:-1] + if empty(values) + continue + endif + let kvs = {} + for i in range(len(keys)) + let kvs[keys[i]] = values[i] + endfor + if has_key(kvs, 'commit') && has_key(kvs, 'path') + let ref_path = kvs.commit . '/' . kvs.path + elseif has_key(kvs, 'commit') && variant[0] ==# 'tree' + let ref_path = kvs.commit . '/' + elseif has_key(kvs, 'commit') + let ref_path = kvs.commit + else + continue + endif + let rev = s:RemoteRefToLocalRef(repo, remote_url, fugitive#UrlDecode(ref_path)) + return [fugitive#Find(rev, repo), empty(rev) ? 0 : +get(kvs, 'line1')] + endfor + endfor + return ['', 0] +endfunction + +function! s:ResolveUrl(target, ...) abort + try + let [url, lnum] = call('fugitive#ResolveUrl', [a:target] + a:000) + if !empty(url) + return [url, lnum] + endif + catch + endtry + return [substitute(a:target, '#.*', '', ''), 0] +endfunction + " Section: Maps let s:ref_header = '\%(Merge\|Rebase\|Upstream\|Pull\|Push\)'