Commit b883d94f969e73a55b83e142510262779f068c84

Authored by Dmitriy Zaporozhets
2 parents 10fac475 f40e0171

Merge branch 'relative_links_in_documentation' of /home/git/repositories/gitlab/gitlabhq

app/helpers/gitlab_markdown_helper.rb
@@ -34,7 +34,8 @@ module GitlabMarkdownHelper @@ -34,7 +34,8 @@ module GitlabMarkdownHelper
34 # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch- 34 # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
35 filter_html: true, 35 filter_html: true,
36 with_toc_data: true, 36 with_toc_data: true,
37 - hard_wrap: true) 37 + hard_wrap: true,
  38 + safe_links_only: true)
38 @markdown = Redcarpet::Markdown.new(gitlab_renderer, 39 @markdown = Redcarpet::Markdown.new(gitlab_renderer,
39 # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use 40 # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
40 no_intra_emphasis: true, 41 no_intra_emphasis: true,
@@ -57,4 +58,97 @@ module GitlabMarkdownHelper @@ -57,4 +58,97 @@ module GitlabMarkdownHelper
57 wiki_page.formatted_content.html_safe 58 wiki_page.formatted_content.html_safe
58 end 59 end
59 end 60 end
  61 +
  62 + # text - whole text from a markdown file
  63 + # project_path_with_namespace - namespace/projectname, eg. gitlabhq/gitlabhq
  64 + # ref - name of the branch or reference, eg. stable
  65 + # requested_path - path of request, eg. doc/api/README.md, used in special case when path is pointing to the .md file were the original request is coming from
  66 + # wiki - whether the markdown is from wiki or not
  67 + def create_relative_links(text, project_path_with_namespace, ref, requested_path, wiki = false)
  68 + paths = extract_paths(text)
  69 + paths.each do |file_path|
  70 + new_path = rebuild_path(project_path_with_namespace, file_path, requested_path, ref)
  71 + # Replacing old string with a new one with brackets ]() to prevent replacing occurence of a word
  72 + # e.g. If we have a markdown like [test](test) this will replace ](test) and not the word test
  73 + text.gsub!("](#{file_path})", "](/#{new_path})")
  74 + end
  75 + text
  76 + end
  77 +
  78 + def extract_paths(markdown_text)
  79 + all_markdown_paths = pick_out_paths(markdown_text)
  80 + paths = remove_empty(all_markdown_paths)
  81 + select_relative(paths)
  82 + end
  83 +
  84 + # Split the markdown text to each line and find all paths, this will match anything with - ]("some_text")
  85 + def pick_out_paths(markdown_text)
  86 + markdown_text.split("\n").map { |text| text.scan(/\]\(([^(]+)\)/) }
  87 + end
  88 +
  89 + # Removes any empty result produced by not matching the regexp
  90 + def remove_empty(paths)
  91 + paths.reject{|l| l.empty? }.flatten
  92 + end
  93 +
  94 + # Reject any path that contains ignored protocol
  95 + # eg. reject "https://gitlab.org} but accept "doc/api/README.md"
  96 + def select_relative(paths)
  97 + paths.reject{|path| ignored_protocols.map{|protocol| path.include?(protocol)}.any?}
  98 + end
  99 +
  100 + def ignored_protocols
  101 + ["http://","https://", "ftp://", "mailto:"]
  102 + end
  103 +
  104 + def rebuild_path(path_with_namespace, path, requested_path, ref)
  105 + file_path = relative_file_path(path, requested_path)
  106 + [
  107 + path_with_namespace,
  108 + path_with_ref(file_path, ref),
  109 + file_path
  110 + ].compact.join("/")
  111 + end
  112 +
  113 + # Checks if the path exists in the repo
  114 + # eg. checks if doc/README.md exists, if it doesn't then it is a wiki link
  115 + def path_with_ref(path, ref)
  116 + if file_exists?(path)
  117 + "#{local_path(path)}/#{correct_ref(ref)}"
  118 + else
  119 + "wikis"
  120 + end
  121 + end
  122 +
  123 + def relative_file_path(path, requested_path)
  124 + nested_path = build_nested_path(path, requested_path)
  125 + return nested_path if file_exists?(nested_path)
  126 + path
  127 + end
  128 +
  129 + # Covering a special case, when the link is referencing file in the same directory eg:
  130 + # If we are at doc/api/README.md and the README.md contains relative links like [Users](users.md)
  131 + # this takes the request path(doc/api/README.md), and replaces the README.md with users.md so the path looks like doc/api/users.md
  132 + def build_nested_path(path, request_path)
  133 + return path unless request_path
  134 + base = request_path.split("/")
  135 + base.pop
  136 + (base + [path]).join("/")
  137 + end
  138 +
  139 + def file_exists?(path)
  140 + return false if path.nil? || path.empty?
  141 + File.exists?(Rails.root.join(path))
  142 + end
  143 +
  144 + # Check if the path is pointing to a directory(tree) or a file(blob)
  145 + # eg. doc/api is directory and doc/README.md is file
  146 + def local_path(path)
  147 + File.directory?(Rails.root.join(path)) ? "tree" : "blob"
  148 + end
  149 +
  150 + # We will assume that if no ref exists we can point to master
  151 + def correct_ref(ref)
  152 + ref ? ref : "master"
  153 + end
60 end 154 end
features/project/source/markdown_render.feature 0 → 100644
@@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
  1 +Feature: Project markdown render
  2 + Background:
  3 + Given I sign in as a user
  4 + And I own project "Delta"
  5 + Given I visit project source page
  6 +
  7 + Scenario: I browse files from master branch
  8 + Then I should see files from repository in master
  9 + And I should see rendered README which contains correct links
  10 + And I click on Gitlab API in README
  11 + Then I should see correct document rendered
  12 +
  13 + Scenario: I view README in master branch
  14 + Then I should see files from repository in master
  15 + And I should see rendered README which contains correct links
  16 + And I click on Rake tasks in README
  17 + Then I should see correct directory rendered
  18 +
  19 + Scenario: I navigate to doc directory to view documentation in master
  20 + And I navigate to the doc/api/README
  21 + And I see correct file rendered
  22 + And I click on users in doc/api/README
  23 + Then I should see the correct document file
  24 +
  25 + Scenario: I navigate to doc directory to view user doc in master
  26 + And I navigate to the doc/api/README
  27 + And I see correct file rendered
  28 + And I click on raketasks in doc/api/README
  29 + Then I should see correct directory rendered
  30 +
  31 + Scenario: I browse files from markdown branch
  32 + When I visit markdown branch
  33 + Then I should see files from repository in markdown branch
  34 + And I should see rendered README which contains correct links
  35 + And I click on Gitlab API in README
  36 + Then I should see correct document rendered for markdown branch
  37 +
  38 + Scenario: I browse directory from markdown branch
  39 + When I visit markdown branch
  40 + Then I should see files from repository in markdown branch
  41 + And I should see rendered README which contains correct links
  42 + And I click on Rake tasks in README
  43 + Then I should see correct directory rendered for markdown branch
  44 +
  45 + Scenario: I navigate to doc directory to view documentation in markdown branch
  46 + When I visit markdown branch
  47 + And I navigate to the doc/api/README
  48 + And I see correct file rendered in markdown branch
  49 + And I click on users in doc/api/README
  50 + Then I should see the users document file in markdown branch
  51 +
  52 + Scenario: I navigate to doc directory to view user doc in markdown branch
  53 + When I visit markdown branch
  54 + And I navigate to the doc/api/README
  55 + And I see correct file rendered in markdown branch
  56 + And I click on raketasks in doc/api/README
  57 + Then I should see correct directory rendered for markdown branch
  58 +
  59 + Scenario: I create a wiki page with different links
  60 + Given I go to wiki page
  61 + And I add various links to the wiki page
  62 + Then Wiki page should have added links
  63 + And I click on test link
  64 + Then I see new wiki page named test
  65 + When I go back to wiki page home
  66 + And I click on GitLab API doc link
  67 + Then I see Gitlab API document
  68 + When I go back to wiki page home
  69 + And I click on Rake tasks link
  70 + Then I see Rake tasks directory
features/steps/project/project_markdown_render.rb 0 → 100644
@@ -0,0 +1,153 @@ @@ -0,0 +1,153 @@
  1 +class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedPaths
  4 +
  5 + And 'I own project "Delta"' do
  6 + @project = Project.find_by_name "Delta"
  7 + @project ||= create(:project_with_code, name: "Delta", namespace: @user.namespace)
  8 + @project.team << [@user, :master]
  9 + end
  10 +
  11 + Then 'I should see files from repository in master' do
  12 + current_path.should == project_tree_path(@project, "master")
  13 + page.should have_content "Gemfile"
  14 + page.should have_content "app"
  15 + page.should have_content "README"
  16 + end
  17 +
  18 + And 'I should see rendered README which contains correct links' do
  19 + page.should have_content "Welcome to GitLab GitLab is a free project and repository management application"
  20 + page.should have_link "GitLab API doc"
  21 + page.should have_link "GitLab API website"
  22 + page.should have_link "Rake tasks"
  23 + page.should have_link "backup and restore procedure"
  24 + end
  25 +
  26 + And 'I click on Gitlab API in README' do
  27 + click_link "GitLab API doc"
  28 + end
  29 +
  30 + Then 'I should see correct document rendered' do
  31 + current_path.should == project_blob_path(@project, "master/doc/api/README.md")
  32 + page.should have_content "All API requests require authentication"
  33 + end
  34 +
  35 + And 'I click on Rake tasks in README' do
  36 + click_link "Rake tasks"
  37 + end
  38 +
  39 + Then 'I should see correct directory rendered' do
  40 + current_path.should == project_tree_path(@project, "master/doc/raketasks")
  41 + page.should have_content "backup_restore.md"
  42 + page.should have_content "maintenance.md"
  43 + end
  44 +
  45 + And 'I navigate to the doc/api/README' do
  46 + click_link "doc"
  47 + click_link "api"
  48 + click_link "README.md"
  49 + end
  50 +
  51 + And 'I see correct file rendered' do
  52 + current_path.should == project_blob_path(@project, "master/doc/api/README.md")
  53 + page.should have_content "Contents"
  54 + page.should have_link "Users"
  55 + page.should have_link "Rake tasks"
  56 + end
  57 +
  58 + And 'I click on users in doc/api/README' do
  59 + click_link "Users"
  60 + end
  61 +
  62 + Then 'I should see the correct document file' do
  63 + current_path.should == project_blob_path(@project, "master/doc/api/users.md")
  64 + page.should have_content "Get a list of users."
  65 + end
  66 +
  67 + And 'I click on raketasks in doc/api/README' do
  68 + click_link "Rake tasks"
  69 + end
  70 +
  71 + When 'I visit markdown branch' do
  72 + visit project_tree_path(@project, "markdown")
  73 + end
  74 +
  75 + Then 'I should see files from repository in markdown branch' do
  76 + current_path.should == project_tree_path(@project, "markdown")
  77 + page.should have_content "Gemfile"
  78 + page.should have_content "app"
  79 + page.should have_content "README"
  80 + end
  81 +
  82 + And 'I see correct file rendered in markdown branch' do
  83 + current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
  84 + page.should have_content "Contents"
  85 + page.should have_link "Users"
  86 + page.should have_link "Rake tasks"
  87 + end
  88 +
  89 + Then 'I should see correct document rendered for markdown branch' do
  90 + current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
  91 + page.should have_content "All API requests require authentication"
  92 + end
  93 +
  94 + Then 'I should see correct directory rendered for markdown branch' do
  95 + current_path.should == project_tree_path(@project, "markdown/doc/raketasks")
  96 + page.should have_content "backup_restore.md"
  97 + page.should have_content "maintenance.md"
  98 + end
  99 +
  100 + Then 'I should see the users document file in markdown branch' do
  101 + current_path.should == project_blob_path(@project, "markdown/doc/api/users.md")
  102 + page.should have_content "Get a list of users."
  103 + end
  104 +
  105 + Given 'I go to wiki page' do
  106 + click_link "Wiki"
  107 + current_path.should == project_wiki_path(@project, "home")
  108 + end
  109 +
  110 + And 'I add various links to the wiki page' do
  111 + fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](doc/api/README.md)\n[Rake tasks](doc/raketasks)\n"
  112 + fill_in "wiki[message]", with: "Adding links to wiki"
  113 + click_button "Create page"
  114 + end
  115 +
  116 + Then 'Wiki page should have added links' do
  117 + current_path.should == project_wiki_path(@project, "home")
  118 + page.should have_content "test GitLab API doc Rake tasks"
  119 + end
  120 +
  121 + And 'I click on test link' do
  122 + click_link "test"
  123 + end
  124 +
  125 + Then 'I see new wiki page named test' do
  126 + current_path.should == project_wiki_path(@project, "test")
  127 + page.should have_content "Editing page"
  128 + end
  129 +
  130 + When 'I go back to wiki page home' do
  131 + visit project_wiki_path(@project, "home")
  132 + current_path.should == project_wiki_path(@project, "home")
  133 + end
  134 +
  135 + And 'I click on GitLab API doc link' do
  136 + click_link "GitLab API"
  137 + end
  138 +
  139 + Then 'I see Gitlab API document' do
  140 + current_path.should == project_blob_path(@project, "master/doc/api/README.md")
  141 + page.should have_content "Status codes"
  142 + end
  143 +
  144 + And 'I click on Rake tasks link' do
  145 + click_link "Rake tasks"
  146 + end
  147 +
  148 + Then 'I see Rake tasks directory' do
  149 + current_path.should == project_tree_path(@project, "master/doc/raketasks")
  150 + page.should have_content "backup_restore.md"
  151 + page.should have_content "maintenance.md"
  152 + end
  153 +end
0 \ No newline at end of file 154 \ No newline at end of file
lib/redcarpet/render/gitlab_html.rb
@@ -6,6 +6,8 @@ class Redcarpet::Render::GitlabHTML &lt; Redcarpet::Render::HTML @@ -6,6 +6,8 @@ class Redcarpet::Render::GitlabHTML &lt; Redcarpet::Render::HTML
6 def initialize(template, options = {}) 6 def initialize(template, options = {})
7 @template = template 7 @template = template
8 @project = @template.instance_variable_get("@project") 8 @project = @template.instance_variable_get("@project")
  9 + @ref = @template.instance_variable_get("@ref")
  10 + @request_path = @template.instance_variable_get("@path")
9 super options 11 super options
10 end 12 end
11 13
@@ -32,7 +34,15 @@ class Redcarpet::Render::GitlabHTML &lt; Redcarpet::Render::HTML @@ -32,7 +34,15 @@ class Redcarpet::Render::GitlabHTML &lt; Redcarpet::Render::HTML
32 h.link_to_gfm(content, link, title: title) 34 h.link_to_gfm(content, link, title: title)
33 end 35 end
34 36
  37 + def preprocess(full_document)
  38 + h.create_relative_links(full_document, @project.path_with_namespace, @ref, @request_path, is_wiki?)
  39 + end
  40 +
35 def postprocess(full_document) 41 def postprocess(full_document)
36 h.gfm(full_document) 42 h.gfm(full_document)
37 end 43 end
  44 +
  45 + def is_wiki?
  46 + @template.instance_variable_get("@wiki")
  47 + end
38 end 48 end
spec/helpers/gitlab_markdown_helper_spec.rb
@@ -406,6 +406,30 @@ describe GitlabMarkdownHelper do @@ -406,6 +406,30 @@ describe GitlabMarkdownHelper do
406 it "should generate absolute urls for emoji" do 406 it "should generate absolute urls for emoji" do
407 markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}") 407 markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}")
408 end 408 end
  409 +
  410 + it "should handle relative urls for a file in master" do
  411 + actual = "[GitLab API doc](doc/api/README.md)\n"
  412 + expected = "<p><a href=\"/#{project.path_with_namespace}/blob/master/doc/api/README.md\">GitLab API doc</a></p>\n"
  413 + markdown(actual).should match(expected)
  414 + end
  415 +
  416 + it "should handle relative urls for a directory in master" do
  417 + actual = "[GitLab API doc](doc/api)\n"
  418 + expected = "<p><a href=\"/#{project.path_with_namespace}/tree/master/doc/api\">GitLab API doc</a></p>\n"
  419 + markdown(actual).should match(expected)
  420 + end
  421 +
  422 + it "should handle absolute urls" do
  423 + actual = "[GitLab](https://www.gitlab.com)\n"
  424 + expected = "<p><a href=\"https://www.gitlab.com\">GitLab</a></p>\n"
  425 + markdown(actual).should match(expected)
  426 + end
  427 +
  428 + it "should handle wiki urls" do
  429 + actual = "[Link](test/link)\n"
  430 + expected = "<p><a href=\"/#{project.path_with_namespace}/wikis/test/link\">Link</a></p>\n"
  431 + markdown(actual).should match(expected)
  432 + end
409 end 433 end
410 434
411 describe "#render_wiki_content" do 435 describe "#render_wiki_content" do
spec/lib/gitlab/satellite/merge_action_spec.rb
@@ -3,7 +3,7 @@ require &#39;spec_helper&#39; @@ -3,7 +3,7 @@ require &#39;spec_helper&#39;
3 describe 'Gitlab::Satellite::MergeAction' do 3 describe 'Gitlab::Satellite::MergeAction' do
4 before(:each) do 4 before(:each) do
5 # TestEnv.init(mailer: false, init_repos: true, repos: true) 5 # TestEnv.init(mailer: false, init_repos: true, repos: true)
6 - @master = ['master', 'bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a'] 6 + @master = ['master', 'b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828']
7 @one_after_stable = ['stable', '6ea87c47f0f8a24ae031c3fff17bc913889ecd00'] #this commit sha is one after stable 7 @one_after_stable = ['stable', '6ea87c47f0f8a24ae031c3fff17bc913889ecd00'] #this commit sha is one after stable
8 @wiki_branch = ['wiki', '635d3e09b72232b6e92a38de6cc184147e5bcb41'] #this is the commit sha where the wiki branch goes off from master 8 @wiki_branch = ['wiki', '635d3e09b72232b6e92a38de6cc184147e5bcb41'] #this is the commit sha where the wiki branch goes off from master
9 @conflicting_metior = ['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f'] #this branch conflicts with the wiki branch 9 @conflicting_metior = ['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f'] #this branch conflicts with the wiki branch
spec/models/project_spec.rb
@@ -132,17 +132,17 @@ describe Project do @@ -132,17 +132,17 @@ describe Project do
132 132
133 it "should close merge request if last commit from source branch was pushed to target branch" do 133 it "should close merge request if last commit from source branch was pushed to target branch" do
134 @merge_request.reloaded_commits 134 @merge_request.reloaded_commits
135 - @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"  
136 - project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user) 135 + @merge_request.last_commit.id.should == "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828"
  136 + project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828", "refs/heads/stable", @key.user)
137 @merge_request.reload 137 @merge_request.reload
138 @merge_request.merged?.should be_true 138 @merge_request.merged?.should be_true
139 end 139 end
140 140
141 it "should update merge request commits with new one if pushed to source branch" do 141 it "should update merge request commits with new one if pushed to source branch" do
142 @merge_request.last_commit.should == nil 142 @merge_request.last_commit.should == nil
143 - project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/master", @key.user) 143 + project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828", "refs/heads/master", @key.user)
144 @merge_request.reload 144 @merge_request.reload
145 - @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" 145 + @merge_request.last_commit.id.should == "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828"
146 end 146 end
147 end 147 end
148 148
spec/seed_project.tar.gz
No preview for this file type