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 34 # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
35 35 filter_html: true,
36 36 with_toc_data: true,
37   - hard_wrap: true)
  37 + hard_wrap: true,
  38 + safe_links_only: true)
38 39 @markdown = Redcarpet::Markdown.new(gitlab_renderer,
39 40 # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
40 41 no_intra_emphasis: true,
... ... @@ -57,4 +58,97 @@ module GitlabMarkdownHelper
57 58 wiki_page.formatted_content.html_safe
58 59 end
59 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 154 end
... ...
features/project/source/markdown_render.feature 0 → 100644
... ... @@ -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 @@
  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 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 def initialize(template, options = {})
7 7 @template = template
8 8 @project = @template.instance_variable_get("@project")
  9 + @ref = @template.instance_variable_get("@ref")
  10 + @request_path = @template.instance_variable_get("@path")
9 11 super options
10 12 end
11 13  
... ... @@ -32,7 +34,15 @@ class Redcarpet::Render::GitlabHTML &lt; Redcarpet::Render::HTML
32 34 h.link_to_gfm(content, link, title: title)
33 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 41 def postprocess(full_document)
36 42 h.gfm(full_document)
37 43 end
  44 +
  45 + def is_wiki?
  46 + @template.instance_variable_get("@wiki")
  47 + end
38 48 end
... ...
spec/helpers/gitlab_markdown_helper_spec.rb
... ... @@ -406,6 +406,30 @@ describe GitlabMarkdownHelper do
406 406 it "should generate absolute urls for emoji" do
407 407 markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}")
408 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 433 end
410 434  
411 435 describe "#render_wiki_content" do
... ...
spec/lib/gitlab/satellite/merge_action_spec.rb
... ... @@ -3,7 +3,7 @@ require &#39;spec_helper&#39;
3 3 describe 'Gitlab::Satellite::MergeAction' do
4 4 before(:each) do
5 5 # TestEnv.init(mailer: false, init_repos: true, repos: true)
6   - @master = ['master', 'bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a']
  6 + @master = ['master', 'b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828']
7 7 @one_after_stable = ['stable', '6ea87c47f0f8a24ae031c3fff17bc913889ecd00'] #this commit sha is one after stable
8 8 @wiki_branch = ['wiki', '635d3e09b72232b6e92a38de6cc184147e5bcb41'] #this is the commit sha where the wiki branch goes off from master
9 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 132  
133 133 it "should close merge request if last commit from source branch was pushed to target branch" do
134 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 137 @merge_request.reload
138 138 @merge_request.merged?.should be_true
139 139 end
140 140  
141 141 it "should update merge request commits with new one if pushed to source branch" do
142 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 144 @merge_request.reload
145   - @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
  145 + @merge_request.last_commit.id.should == "b1e6a9dbf1c85e6616497a5e7bad9143a4bd0828"
146 146 end
147 147 end
148 148  
... ...
spec/seed_project.tar.gz
No preview for this file type