Commit 0e6f639a authored by Ethan Reesor's avatar Ethan Reesor Committed by Stan Hu

Support go-source meta tag for godoc.org

parent 5e60ca15
---
title: Support go-source meta tag for godoc.org
merge_request: 19888
author: Ethan Reesor (@firelizzard)
type: changed
......@@ -200,9 +200,14 @@ When [renaming a user](../profile/index.md#changing-your-username),
## Use your project as a Go package
Any project can be used as a Go package including private projects in subgroups. To use packages
hosted in private projects with the `go get` command, use a [`.netrc` file](https://ec.haxx.se/usingcurl-netrc.html)
and a [personal access token](../profile/personal_access_tokens.md) in the password field.
Any project can be used as a Go package including private projects in subgroups.
GitLab responds correctly to `go get` and `godoc.org` discovery requests,
including the [`go-import`](https://golang.org/cmd/go/#hdr-Remote_import_paths)
and [`go-source`](https://github.com/golang/gddo/wiki/Source-Code-Links) meta
tags, respectively. To use packages hosted in private projects with the `go get`
command, use a [`.netrc` file](https://ec.haxx.se/usingcurl-netrc.html) and a
[personal access token](../profile/personal_access_tokens.md) in the password
field.
For example:
......
......@@ -25,13 +25,13 @@ module Gitlab
def render_go_doc(request)
return unless go_request?(request)
path = project_path(request)
path, branch = project_path(request)
return unless path
body = go_body(path)
body, code = go_response(path, branch)
return unless body
response = Rack::Response.new(body, 200, { 'Content-Type' => 'text/html' })
response = Rack::Response.new(body, code, { 'Content-Type' => 'text/html' })
response.finish
end
......@@ -39,8 +39,15 @@ module Gitlab
request["go-get"].to_i == 1 && request.env["PATH_INFO"].present?
end
def go_body(path)
def go_response(path, branch)
config = Gitlab.config
body_tag = content_tag :body, "go get #{config.gitlab.url}/#{path}"
unless branch
html_tag = content_tag :html, body_tag
return html_tag, 404
end
project_url = Gitlab::Utils.append_path(config.gitlab.url, path)
import_prefix = strip_url(project_url.to_s)
......@@ -52,9 +59,11 @@ module Gitlab
"#{project_url}.git"
end
meta_tag = tag :meta, name: 'go-import', content: "#{import_prefix} git #{repository_url}"
head_tag = content_tag :head, meta_tag
content_tag :html, head_tag
meta_import_tag = tag :meta, name: 'go-import', content: "#{import_prefix} git #{repository_url}"
meta_source_tag = tag :meta, name: 'go-source', content: "#{import_prefix} #{project_url} #{project_url}/tree/#{branch}{/dir} #{project_url}/blob/#{branch}{/dir}/{file}#L{line}"
head_tag = content_tag :head, meta_import_tag + meta_source_tag
html_tag = content_tag :html, head_tag + body_tag
[html_tag, 200]
end
def strip_url(url)
......@@ -80,9 +89,6 @@ module Gitlab
path_segments = path.split('/')
simple_project_path = path_segments.first(2).join('/')
# If the path is at most 2 segments long, it is a simple `namespace/project` path and we're done
return simple_project_path if path_segments.length <= 2
project_paths = []
begin
project_paths << path_segments.join('/')
......@@ -94,7 +100,7 @@ module Gitlab
if project
# If a project is found and the user has access, we return the full project path
project.full_path
return project.full_path, project.default_branch
else
# If not, we return the first two components as if it were a simple `namespace/project` path,
# so that we don't reveal the existence of a nested project the user doesn't have access to.
......@@ -105,7 +111,7 @@ module Gitlab
# `go get gitlab.com/group/subgroup/project/subpackage` will not work for private projects.
# `go get gitlab.com/group/subgroup/project.git/subpackage` will work, since Go is smart enough
# to figure that out. `import 'gitlab.com/...'` behaves the same as `go get`.
simple_project_path
return simple_project_path, 'master'
end
end
......
......@@ -30,13 +30,13 @@ describe Gitlab::Middleware::Go do
shared_examples 'go-get=1' do |enabled_protocol:|
context 'with simple 2-segment project path' do
let!(:project) { create(:project, :private) }
let!(:project) { create(:project, :private, :repository) }
context 'with subpackages' do
let(:path) { "#{project.full_path}/subpackage" }
it 'returns the full project path' do
expect_response_with_path(go, enabled_protocol, project.full_path)
expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
end
end
......@@ -44,19 +44,19 @@ describe Gitlab::Middleware::Go do
let(:path) { project.full_path }
it 'returns the full project path' do
expect_response_with_path(go, enabled_protocol, project.full_path)
expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
end
end
end
context 'with a nested project path' do
let(:group) { create(:group, :nested) }
let!(:project) { create(:project, :public, namespace: group) }
let!(:project) { create(:project, :public, :repository, namespace: group) }
shared_examples 'a nested project' do
context 'when the project is public' do
it 'returns the full project path' do
expect_response_with_path(go, enabled_protocol, project.full_path)
expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
end
end
......@@ -67,7 +67,7 @@ describe Gitlab::Middleware::Go do
shared_examples 'unauthorized' do
it 'returns the 2-segment group path' do
expect_response_with_path(go, enabled_protocol, group.full_path)
expect_response_with_path(go, enabled_protocol, group.full_path, project.default_branch)
end
end
......@@ -85,7 +85,7 @@ describe Gitlab::Middleware::Go do
shared_examples 'authenticated' do
context 'with access to the project' do
it 'returns the full project path' do
expect_response_with_path(go, enabled_protocol, project.full_path)
expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
end
end
......@@ -160,6 +160,36 @@ describe Gitlab::Middleware::Go do
go
end
end
context 'with a public project without a repository' do
let!(:project) { create(:project, :public) }
let(:path) { project.full_path }
it 'returns 404' do
response = go
expect(response[0]).to eq(404)
expect(response[1]['Content-Type']).to eq('text/html')
expected_body = %{<html><body>go get #{Gitlab.config.gitlab.url}/#{project.full_path}</body></html>}
expect(response[2].body).to eq([expected_body])
end
end
context 'with a non-standard head' do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, :repository) }
let(:path) { project.full_path }
let(:default_branch) { 'default_branch' }
before do
project.add_maintainer(user)
project.repository.add_branch(user, default_branch, 'master')
project.change_head(default_branch)
end
it 'returns the full project path' do
expect_response_with_path(go, enabled_protocol, project.full_path, default_branch)
end
end
end
context 'with SSH disabled' do
......@@ -199,16 +229,17 @@ describe Gitlab::Middleware::Go do
middleware.call(env)
end
def expect_response_with_path(response, protocol, path)
def expect_response_with_path(response, protocol, path, branch)
repository_url = case protocol
when :ssh
"ssh://#{Gitlab.config.gitlab.user}@#{Gitlab.config.gitlab.host}/#{path}.git"
when :http, nil
"http://#{Gitlab.config.gitlab.host}/#{path}.git"
end
project_url = "http://#{Gitlab.config.gitlab.host}/#{path}"
expect(response[0]).to eq(200)
expect(response[1]['Content-Type']).to eq('text/html')
expected_body = %{<html><head><meta name="go-import" content="#{Gitlab.config.gitlab.host}/#{path} git #{repository_url}" /></head></html>}
expected_body = %{<html><head><meta name="go-import" content="#{Gitlab.config.gitlab.host}/#{path} git #{repository_url}" /><meta name="go-source" content="#{Gitlab.config.gitlab.host}/#{path} #{project_url} #{project_url}/tree/#{branch}{/dir} #{project_url}/blob/#{branch}{/dir}/{file}#L{line}" /></head><body>go get #{Gitlab.config.gitlab.url}/#{path}</body></html>}
expect(response[2].body).to eq([expected_body])
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment