Commit bcc4e4dc7ed0740e92a61fc82c3c669f8f2d8d30

Authored by Dmitriy Zaporozhets
2 parents 211e435a e0af7cef

Merge branch 'gist' of https://github.com/Andrew8xx8/gitlabhq into Andrew8xx8-gist

Conflicts:
	Gemfile.lock
	app/models/ability.rb
	app/models/project.rb
	app/views/snippets/_form.html.haml
	db/schema.rb
	features/steps/shared/paths.rb
	spec/factories.rb
	spec/models/project_spec.rb
Showing 59 changed files with 967 additions and 209 deletions   Show diff stats
app/controllers/projects/application_controller.rb
1 1 class Projects::ApplicationController < ApplicationController
2   -
3   - before_filter :authorize_admin_team_member!
4   -
5   - protected
6   -
7   - def user_team
8   - @team ||= UserTeam.find_by_path(params[:id])
9   - end
10   -
  2 + before_filter :project
  3 + before_filter :repository
11 4 end
... ...
app/controllers/projects/snippets_controller.rb 0 → 100644
... ... @@ -0,0 +1,91 @@
  1 +class Projects::SnippetsController < Projects::ApplicationController
  2 + before_filter :module_enabled
  3 + before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
  4 +
  5 + # Allow read any snippet
  6 + before_filter :authorize_read_project_snippet!
  7 +
  8 + # Allow write(create) snippet
  9 + before_filter :authorize_write_project_snippet!, only: [:new, :create]
  10 +
  11 + # Allow modify snippet
  12 + before_filter :authorize_modify_project_snippet!, only: [:edit, :update]
  13 +
  14 + # Allow destroy snippet
  15 + before_filter :authorize_admin_project_snippet!, only: [:destroy]
  16 +
  17 + layout 'project_resource'
  18 +
  19 + respond_to :html
  20 +
  21 + def index
  22 + @snippets = @project.snippets.fresh.non_expired
  23 + end
  24 +
  25 + def new
  26 + @snippet = @project.snippets.build
  27 + end
  28 +
  29 + def create
  30 + @snippet = @project.snippets.build(params[:project_snippet])
  31 + @snippet.author = current_user
  32 +
  33 + if @snippet.save
  34 + redirect_to project_snippet_path(@project, @snippet)
  35 + else
  36 + respond_with(@snippet)
  37 + end
  38 + end
  39 +
  40 + def edit
  41 + end
  42 +
  43 + def update
  44 + if @snippet.update_attributes(params[:project_snippet])
  45 + redirect_to project_snippet_path(@project, @snippet)
  46 + else
  47 + respond_with(@snippet)
  48 + end
  49 + end
  50 +
  51 + def show
  52 + @note = @project.notes.new(noteable: @snippet)
  53 + @target_type = :snippet
  54 + @target_id = @snippet.id
  55 + end
  56 +
  57 + def destroy
  58 + return access_denied! unless can?(current_user, :admin_project_snippet, @snippet)
  59 +
  60 + @snippet.destroy
  61 +
  62 + redirect_to project_snippets_path(@project)
  63 + end
  64 +
  65 + def raw
  66 + send_data(
  67 + @snippet.content,
  68 + type: "text/plain",
  69 + disposition: 'inline',
  70 + filename: @snippet.file_name
  71 + )
  72 + end
  73 +
  74 + protected
  75 +
  76 + def snippet
  77 + @snippet ||= @project.snippets.find(params[:id])
  78 + end
  79 +
  80 + def authorize_modify_project_snippet!
  81 + return render_404 unless can?(current_user, :modify_project_snippet, @snippet)
  82 + end
  83 +
  84 + def authorize_admin_project_snippet!
  85 + return render_404 unless can?(current_user, :admin_project_snippet, @snippet)
  86 + end
  87 +
  88 + def module_enabled
  89 + return render_404 unless @project.snippets_enabled
  90 + end
  91 +end
... ...
app/controllers/projects/teams_controller.rb
1 1 class Projects::TeamsController < Projects::ApplicationController
2 2  
  3 + before_filter :authorize_admin_team_member!
  4 +
3 5 def available
4 6 @teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams
5 7 @teams = @teams.without_project(project)
... ... @@ -24,4 +26,9 @@ class Projects::TeamsController &lt; Projects::ApplicationController
24 26 redirect_to project_team_index_path(project)
25 27 end
26 28  
  29 + protected
  30 +
  31 + def user_team
  32 + @team ||= UserTeam.find_by_path(params[:id])
  33 + end
27 34 end
... ...
app/controllers/snippets_controller.rb
1   -class SnippetsController < ProjectResourceController
2   - before_filter :module_enabled
  1 +class SnippetsController < ApplicationController
3 2 before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
4 3  
5   - # Allow read any snippet
6   - before_filter :authorize_read_snippet!
7   -
8   - # Allow write(create) snippet
9   - before_filter :authorize_write_snippet!, only: [:new, :create]
10   -
11 4 # Allow modify snippet
12 5 before_filter :authorize_modify_snippet!, only: [:edit, :update]
13 6  
... ... @@ -17,22 +10,38 @@ class SnippetsController &lt; ProjectResourceController
17 10 respond_to :html
18 11  
19 12 def index
20   - @snippets = @project.snippets.fresh.non_expired
  13 + @snippets = Snippet.public.fresh.non_expired.page(params[:page]).per(20)
  14 + end
  15 +
  16 + def user_index
  17 + @user = User.find_by_username(params[:username])
  18 +
  19 + @snippets = @current_user.snippets.fresh.non_expired
  20 +
  21 + @snippets = case params[:scope]
  22 + when 'public' then
  23 + @snippets.public
  24 + when 'private' then
  25 + @snippets.private
  26 + else
  27 + @snippets
  28 + end
  29 +
  30 + @snippets = @snippets.page(params[:page]).per(20)
21 31 end
22 32  
23 33 def new
24   - @snippet = @project.snippets.new
  34 + @snippet = PersonalSnippet.new
25 35 end
26 36  
27 37 def create
28   - @snippet = @project.snippets.new(params[:snippet])
  38 + @snippet = PersonalSnippet.new(params[:personal_snippet])
29 39 @snippet.author = current_user
30   - @snippet.save
31 40  
32   - if @snippet.valid?
33   - redirect_to [@project, @snippet]
  41 + if @snippet.save
  42 + redirect_to snippet_path(@snippet)
34 43 else
35   - respond_with(@snippet)
  44 + respond_with @snippet
36 45 end
37 46 end
38 47  
... ... @@ -40,27 +49,22 @@ class SnippetsController &lt; ProjectResourceController
40 49 end
41 50  
42 51 def update
43   - @snippet.update_attributes(params[:snippet])
44   -
45   - if @snippet.valid?
46   - redirect_to [@project, @snippet]
  52 + if @snippet.update_attributes(params[:personal_snippet])
  53 + redirect_to snippet_path(@snippet)
47 54 else
48   - respond_with(@snippet)
  55 + respond_with @snippet
49 56 end
50 57 end
51 58  
52 59 def show
53   - @note = @project.notes.new(noteable: @snippet)
54   - @target_type = :snippet
55   - @target_id = @snippet.id
56 60 end
57 61  
58 62 def destroy
59   - return access_denied! unless can?(current_user, :admin_snippet, @snippet)
  63 + return access_denied! unless can?(current_user, :admin_personal_snippet, @snippet)
60 64  
61 65 @snippet.destroy
62 66  
63   - redirect_to project_snippets_path(@project)
  67 + redirect_to snippets_path
64 68 end
65 69  
66 70 def raw
... ... @@ -75,18 +79,14 @@ class SnippetsController &lt; ProjectResourceController
75 79 protected
76 80  
77 81 def snippet
78   - @snippet ||= @project.snippets.find(params[:id])
  82 + @snippet ||= PersonalSnippet.find(params[:id])
79 83 end
80 84  
81 85 def authorize_modify_snippet!
82   - return render_404 unless can?(current_user, :modify_snippet, @snippet)
  86 + return render_404 unless can?(current_user, :modify_personal_snippet, @snippet)
83 87 end
84 88  
85 89 def authorize_admin_snippet!
86   - return render_404 unless can?(current_user, :admin_snippet, @snippet)
87   - end
88   -
89   - def module_enabled
90   - return render_404 unless @project.snippets_enabled
  90 + return render_404 unless can?(current_user, :admin_personal_snippet, @snippet)
91 91 end
92 92 end
... ...
app/helpers/tab_helper.rb
... ... @@ -73,7 +73,7 @@ module TabHelper
73 73 end
74 74  
75 75 def project_tab_class
76   - return "active" if current_page?(controller: "projects", action: :edit, id: @project)
  76 + return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
77 77  
78 78 if ['services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
79 79 "active"
... ...
app/models/ability.rb
... ... @@ -7,7 +7,8 @@ class Ability
7 7 when "Project" then project_abilities(user, subject)
8 8 when "Issue" then issue_abilities(user, subject)
9 9 when "Note" then note_abilities(user, subject)
10   - when "Snippet" then snippet_abilities(user, subject)
  10 + when "ProjectSnippet" then project_snippet_abilities(user, subject)
  11 + when "PersonalSnippet" then personal_snippet_abilities(user, subject)
11 12 when "MergeRequest" then merge_request_abilities(user, subject)
12 13 when "Group", "Namespace" then group_abilities(user, subject)
13 14 when "UserTeam" then user_team_abilities(user, subject)
... ... @@ -54,7 +55,7 @@ class Ability
54 55 :read_wiki,
55 56 :read_issue,
56 57 :read_milestone,
57   - :read_snippet,
  58 + :read_project_snippet,
58 59 :read_team_member,
59 60 :read_merge_request,
60 61 :read_note,
... ... @@ -67,8 +68,8 @@ class Ability
67 68 def project_report_rules
68 69 project_guest_rules + [
69 70 :download_code,
70   - :write_snippet,
71 71 :fork_project
  72 + :write_project_snippet
72 73 ]
73 74 end
74 75  
... ... @@ -84,11 +85,11 @@ class Ability
84 85 project_dev_rules + [
85 86 :push_code_to_protected_branches,
86 87 :modify_issue,
87   - :modify_snippet,
  88 + :modify_project_snippet,
88 89 :modify_merge_request,
89 90 :admin_issue,
90 91 :admin_milestone,
91   - :admin_snippet,
  92 + :admin_project_snippet,
92 93 :admin_team_member,
93 94 :admin_merge_request,
94 95 :admin_note,
... ... @@ -135,8 +136,7 @@ class Ability
135 136 rules.flatten
136 137 end
137 138  
138   -
139   - [:issue, :note, :snippet, :merge_request].each do |name|
  139 + [:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name|
140 140 define_method "#{name}_abilities" do |user, subject|
141 141 if subject.author == user
142 142 [
... ...
app/models/event.rb
... ... @@ -241,6 +241,10 @@ class Event &lt; ActiveRecord::Base
241 241 target.noteable_type == "Commit"
242 242 end
243 243  
  244 + def note_project_snippet?
  245 + target.noteable_type == "Snippet"
  246 + end
  247 +
244 248 def note_target
245 249 target.noteable
246 250 end
... ...
app/models/note.rb
... ... @@ -159,4 +159,10 @@ class Note &lt; ActiveRecord::Base
159 159 "wall"
160 160 end
161 161 end
  162 +
  163 + # FIXME: Hack for polymorphic associations with STI
  164 + # For more information wisit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
  165 + def noteable_type=(sType)
  166 + super(sType.to_s.classify.constantize.base_class.to_s)
  167 + end
162 168 end
... ...
app/models/personal_snippet.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: snippets
  4 +#
  5 +# id :integer not null, primary key
  6 +# title :string(255)
  7 +# content :text
  8 +# author_id :integer not null
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# file_name :string(255)
  13 +# expires_at :datetime
  14 +# type :string(255)
  15 +# private :boolean
  16 +
  17 +class PersonalSnippet < Snippet
  18 +end
... ...
app/models/project.rb
... ... @@ -57,7 +57,7 @@ class Project &lt; ActiveRecord::Base
57 57 has_many :milestones, dependent: :destroy
58 58 has_many :users_projects, dependent: :destroy
59 59 has_many :notes, dependent: :destroy
60   - has_many :snippets, dependent: :destroy
  60 + has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
61 61 has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
62 62 has_many :protected_branches, dependent: :destroy
63 63 has_many :user_team_project_relationships, dependent: :destroy
... ...
app/models/project_snippet.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: snippets
  4 +#
  5 +# id :integer not null, primary key
  6 +# title :string(255)
  7 +# content :text
  8 +# author_id :integer not null
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# file_name :string(255)
  13 +# expires_at :datetime
  14 +# type :string(255)
  15 +# private :boolean
  16 +
  17 +class ProjectSnippet < Snippet
  18 + belongs_to :project
  19 + belongs_to :author, class_name: "User"
  20 +
  21 + validates :project, presence: true
  22 +
  23 + # Scopes
  24 + scope :fresh, -> { order("created_at DESC") }
  25 + scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
  26 + scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
  27 +end
... ...
app/models/snippet.rb
... ... @@ -11,29 +11,31 @@
11 11 # updated_at :datetime not null
12 12 # file_name :string(255)
13 13 # expires_at :datetime
14   -#
  14 +# type :string(255)
  15 +# private :boolean
15 16  
16 17 class Snippet < ActiveRecord::Base
17 18 include Linguist::BlobHelper
18 19  
19   - attr_accessible :title, :content, :file_name, :expires_at
  20 + attr_accessible :title, :content, :file_name, :expires_at, :private
20 21  
21   - belongs_to :project
22 22 belongs_to :author, class_name: "User"
  23 +
23 24 has_many :notes, as: :noteable, dependent: :destroy
24 25  
25 26 delegate :name, :email, to: :author, prefix: true, allow_nil: true
26 27  
27 28 validates :author, presence: true
28   - validates :project, presence: true
29 29 validates :title, presence: true, length: { within: 0..255 }
30 30 validates :file_name, presence: true, length: { within: 0..255 }
31 31 validates :content, presence: true
32 32  
33 33 # Scopes
34   - scope :fresh, -> { order("created_at DESC") }
35   - scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
  34 + scope :public, -> { where(private: false) }
  35 + scope :private, -> { where(private: true) }
  36 + scope :fresh, -> { order("created_at DESC") }
36 37 scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
  38 + scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
37 39  
38 40 def self.content_types
39 41 [
... ...
app/models/user.rb
... ... @@ -78,6 +78,7 @@ class User &lt; ActiveRecord::Base
78 78 has_many :team_projects, through: :user_team_project_relationships
79 79  
80 80 # Projects
  81 + has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
81 82 has_many :users_projects, dependent: :destroy
82 83 has_many :issues, dependent: :destroy, foreign_key: :author_id
83 84 has_many :notes, dependent: :destroy, foreign_key: :author_id
... ...
app/views/events/event/_note.html.haml
... ... @@ -5,6 +5,10 @@
5 5 - if event.note_commit?
6 6 = event.note_target_type
7 7 = link_to event.note_short_commit_id, project_commit_path(event.project, event.note_commit_id), class: "commit_short_id"
  8 + - if event.note_project_snippet?
  9 + = link_to project_snippet_path(event.project, event.note_target) do
  10 + %strong
  11 + #{event.note_target_type} ##{truncate event.note_target_id}
8 12 - else
9 13 = link_to [event.project, event.note_target] do
10 14 %strong
... ...
app/views/layouts/snippets.html.haml 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +!!! 5
  2 +%html{ lang: "en"}
  3 + = render "layouts/head", title: "Snipepts"
  4 + %body{class: "#{app_theme} application"}
  5 + = render "layouts/head_panel", title: "Snippets"
  6 + = render "layouts/flash"
  7 + .container
  8 + %ul.main_menu
  9 + = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
  10 + = link_to root_path, title: "Back to dashboard" do
  11 + %i.icon-arrow-left
  12 + = nav_link(path: 'snippet#new') do
  13 + = link_to new_snippet_path do
  14 + New snippet
  15 + = nav_link(path: 'snippets#user_index') do
  16 + = link_to user_snippets_path(@current_user) do
  17 + My snippets
  18 + = nav_link(path: 'snippets#index') do
  19 + = link_to snippets_path do
  20 + Discover snippets
  21 +
  22 + .content= yield
... ...
app/views/projects/snippets/_blob.html.haml 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +.file_holder
  2 + .file_title
  3 + %i.icon-file
  4 + %strong= @snippet.file_name
  5 + %span.options
  6 + = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank"
  7 + .file_content.code
  8 + - unless @snippet.content.empty?
  9 + %div{class: user_color_scheme_class}
  10 + = raw @snippet.colorize(formatter: :gitlab)
  11 + - else
  12 + %p.nothing_here_message Empty file
... ...
app/views/projects/snippets/_form.html.haml 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +%h3.page_title
  2 + = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}"
  3 +%hr
  4 +.snippet-form-holder
  5 + = form_for [@project, @snippet], as: :project_snippet, url: url do |f|
  6 + -if @snippet.errors.any?
  7 + .alert.alert-error
  8 + %ul
  9 + - @snippet.errors.full_messages.each do |msg|
  10 + %li= msg
  11 +
  12 + .clearfix
  13 + = f.label :title
  14 + .input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
  15 + .clearfix
  16 + = f.label "Lifetime"
  17 + .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
  18 + .clearfix
  19 + .file-editor
  20 + = f.label :file_name, "File"
  21 + .input
  22 + .file_holder.snippet
  23 + .file_title
  24 + = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true
  25 + .file_content.code
  26 + %pre#editor= @snippet.content
  27 + = f.hidden_field :content, class: 'snippet-file-content'
  28 +
  29 + .form-actions
  30 + = f.submit 'Save', class: "btn-save btn"
  31 + = link_to "Cancel", project_snippets_path(@project), class: " btn"
  32 + - unless @snippet.new_record?
  33 + .pull-right= link_to 'Destroy', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
  34 +
  35 +
  36 +:javascript
  37 + var editor = ace.edit("editor");
  38 + $(".snippet-form-holder form").submit(function(){
  39 + $(".snippet-file-content").val(editor.getValue());
  40 + });
  41 +
... ...
app/views/projects/snippets/_snippet.html.haml 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +%tr
  2 + %td
  3 + = image_tag gravatar_icon(snippet.author_email), class: "avatar s24"
  4 + %a{href: project_snippet_path(snippet.project, snippet)}
  5 + %strong= truncate(snippet.title, length: 60)
  6 + %td
  7 + = snippet.file_name
  8 + %td
  9 + %span.cgray
  10 + - if snippet.expires_at
  11 + = snippet.expires_at.to_date.to_s(:short)
  12 + - else
  13 + Never
... ...
app/views/projects/snippets/edit.html.haml 0 → 100644
... ... @@ -0,0 +1 @@
  1 += render "projects/snippets/form", url: project_snippet_path(@project, @snippet)
... ...
app/views/projects/snippets/index.html.haml 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +%h3.page_title
  2 + Snippets
  3 + %small share code pastes with others out of git repository
  4 +
  5 + - if can? current_user, :write_project_snippet, @project
  6 + = link_to new_project_snippet_path(@project), class: "btn btn-small add_new pull-right", title: "New Snippet" do
  7 + Add new snippet
  8 +%br
  9 +%table
  10 + %thead
  11 + %tr
  12 + %th Title
  13 + %th File Name
  14 + %th Expires At
  15 + = render partial: "projects/snippets/snippet", collection: @snippets
  16 + - if @snippets.empty?
  17 + %tr
  18 + %td{colspan: 3}
  19 + %h3.nothing_here_message Nothing here.
... ...
app/views/projects/snippets/new.html.haml 0 → 100644
... ... @@ -0,0 +1 @@
  1 += render "projects/snippets/form", url: project_snippets_path(@project, @snippet)
... ...
app/views/projects/snippets/show.html.haml 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +%h3.page_title
  2 + = @snippet.title
  3 + %small= @snippet.file_name
  4 + - if can?(current_user, :admin_project_snippet, @project) || @snippet.author == current_user
  5 + = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small pull-right", title: 'Edit Snippet'
  6 +
  7 +%br
  8 +%div= render 'projects/snippets/blob'
  9 +%div#notes= render "notes/notes_with_form"
... ...
app/views/snippets/_blob.html.haml
... ... @@ -3,7 +3,7 @@
3 3 %i.icon-file
4 4 %strong= @snippet.file_name
5 5 %span.options
6   - = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank"
  6 + = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-tiny", target: "_blank"
7 7 .file_content.code
8 8 - unless @snippet.content.empty?
9 9 %div{class: user_color_scheme_class}
... ...
app/views/snippets/_form.html.haml
... ... @@ -2,7 +2,7 @@
2 2 = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}"
3 3 %hr
4 4 .snippet-form-holder
5   - = form_for [@project, @snippet] do |f|
  5 + = form_for @snippet, as: :personal_snippet, url: url do |f|
6 6 -if @snippet.errors.any?
7 7 .alert.alert-error
8 8 %ul
... ... @@ -13,6 +13,9 @@
13 13 = f.label :title
14 14 .input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
15 15 .clearfix
  16 + = f.label "Private?"
  17 + .input= f.check_box :private, {class: ''}
  18 + .clearfix
16 19 = f.label "Lifetime"
17 20 .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
18 21 .clearfix
... ... @@ -28,9 +31,9 @@
28 31  
29 32 .form-actions
30 33 = f.submit 'Save', class: "btn-save btn"
31   - = link_to "Cancel", project_snippets_path(@project), class: " btn"
  34 + = link_to "Cancel", snippets_path(@project), class: " btn"
32 35 - unless @snippet.new_record?
33   - .pull-right= link_to 'Destroy', [@project, @snippet], confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
  36 + .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
34 37  
35 38  
36 39 :javascript
... ...
app/views/snippets/_snippet.html.haml
1 1 %tr
2 2 %td
  3 + - if snippet.private?
  4 + %i.icon-lock
  5 + - else
  6 + %i.icon-globe
3 7 = image_tag gravatar_icon(snippet.author_email), class: "avatar s24"
4   - %a{href: project_snippet_path(snippet.project, snippet)}
5   - %strong= truncate(snippet.title, length: 60)
  8 + - if snippet.project_id?
  9 + %a{href: project_snippet_path(snippet.project, snippet)}
  10 + %strong= truncate(snippet.title, length: 60)
  11 + - else
  12 + %a{href: snippet_path(snippet)}
  13 + %strong= truncate(snippet.title, length: 60)
6 14 %td
7 15 = snippet.file_name
8 16 %td
... ... @@ -11,3 +19,6 @@
11 19 = snippet.expires_at.to_date.to_s(:short)
12 20 - else
13 21 Never
  22 + %td
  23 + - if snippet.project_id?
  24 + = link_to snippet.project.name, project_path(snippet.project)
... ...
app/views/snippets/_snippets.html.haml 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +%table
  2 + %thead
  3 + %tr
  4 + %th Title
  5 + %th File Name
  6 + %th Expires At
  7 + %th Project
  8 +
  9 + = render partial: 'snippet', collection: @snippets
  10 + - if @snippets.empty?
  11 + %tr
  12 + %td{colspan: 4}
  13 + %h3.nothing_here_message Nothing here.
  14 +
  15 += paginate @snippets
... ...
app/views/snippets/edit.html.haml
1   -= render "snippets/form"
  1 += render "snippets/form", url: snippet_path(@snippet)
... ...
app/views/snippets/index.html.haml
1 1 %h3.page_title
2   - Snippets
  2 + Public snippets
3 3 %small share code pastes with others out of git repository
  4 + = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
  5 + Add new snippet
  6 +
  7 +%hr
  8 +.row
  9 + .span12
  10 + = render 'snippets'
4 11  
5   - - if can? current_user, :write_snippet, @project
6   - = link_to new_project_snippet_path(@project), class: "btn btn-small add_new pull-right", title: "New Snippet" do
7   - Add new snippet
8   -%br
9   -%table
10   - %thead
11   - %tr
12   - %th Title
13   - %th File Name
14   - %th Expires At
15   - = render @snippets
16   - - if @snippets.empty?
17   - %tr
18   - %td{colspan: 3}
19   - %h3.nothing_here_message Nothing here.
... ...
app/views/snippets/new.html.haml
1   -= render "snippets/form"
  1 += render "snippets/form", url: snippets_path(@snippet)
... ...
app/views/snippets/show.html.haml
1 1 %h3.page_title
  2 + - if @snippet.private?
  3 + %i.icon-lock
  4 + - else
  5 + %i.icon-globe
  6 +
2 7 = @snippet.title
3 8 %small= @snippet.file_name
4   - - if can?(current_user, :admin_snippet, @project) || @snippet.author == current_user
5   - = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small pull-right", title: 'Edit Snippet'
  9 + - if @snippet.author == current_user
  10 + = link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-small pull-right", title: 'Edit Snippet'
6 11  
7 12 %br
8 13 %div= render 'blob'
9   -%div#notes= render "notes/notes_with_form"
... ...
app/views/snippets/user_index.html.haml 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +%h3.page_title
  2 + Snippets by
  3 + = @user.name
  4 + %small share code pastes with others out of git repository
  5 + = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
  6 + Add new snippet
  7 +
  8 +%hr
  9 +.row
  10 + .span3
  11 + %ul.nav.nav-pills.nav-stacked
  12 + = nav_tab :scope, nil do
  13 + = link_to "All", user_snippets_path(@user)
  14 + = nav_tab :scope, 'private' do
  15 + = link_to "Private", user_snippets_path(@user, scope: 'private')
  16 + = nav_tab :scope, 'public' do
  17 + = link_to "Public", user_snippets_path(@user, scope: 'public')
  18 +
  19 + .span9
  20 + = render 'snippets'
... ...
config/routes.rb
... ... @@ -39,6 +39,16 @@ Gitlab::Application.routes.draw do
39 39 get 'help/workflow' => 'help#workflow'
40 40  
41 41 #
  42 + # Global snippets
  43 + #
  44 + resources :snippets do
  45 + member do
  46 + get "raw"
  47 + end
  48 + end
  49 + get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ }
  50 +
  51 + #
42 52 # Public namespace
43 53 #
44 54 namespace :public do
... ... @@ -182,6 +192,14 @@ Gitlab::Application.routes.draw do
182 192 resources :graph, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
183 193 match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
184 194  
  195 + scope module: :projects do
  196 + resources :snippets do
  197 + member do
  198 + get "raw"
  199 + end
  200 + end
  201 + end
  202 +
185 203 resources :wikis, only: [:show, :edit, :destroy, :create] do
186 204 collection do
187 205 get :pages
... ... @@ -255,19 +273,12 @@ Gitlab::Application.routes.draw do
255 273 end
256 274 end
257 275  
258   - resources :snippets do
259   - member do
260   - get "raw"
261   - end
262   - end
263   -
264 276 resources :hooks, only: [:index, :create, :destroy] do
265 277 member do
266 278 get :test
267 279 end
268 280 end
269 281  
270   -
271 282 resources :team, controller: 'team_members', only: [:index]
272 283 resources :milestones, except: [:destroy]
273 284  
... ...
db/migrate/20130323174317_add_private_to_snippets.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddPrivateToSnippets < ActiveRecord::Migration
  2 + def change
  3 + add_column :snippets, :private, :boolean, null: false, default: true
  4 + end
  5 +end
... ...
db/migrate/20130324151736_add_type_to_snippets.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddTypeToSnippets < ActiveRecord::Migration
  2 + def change
  3 + add_column :snippets, :type, :string
  4 + end
  5 +end
... ...
db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class ChangeProjectIdToNullInSnipepts < ActiveRecord::Migration
  2 + def up
  3 + change_column :snippets, :project_id, :integer, :null => true
  4 + end
  5 +
  6 + def down
  7 + change_column :snippets, :project_id, :integer, :null => false
  8 + end
  9 +end
... ...
db/migrate/20130324203535_add_type_value_for_snippets.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class AddTypeValueForSnippets < ActiveRecord::Migration
  2 + def up
  3 + Snippet.where("project_id IS NOT NULL").update_all(type: 'ProjectSnippet')
  4 + end
  5 +
  6 + def down
  7 + end
  8 +end
... ...
db/schema.rb
... ... @@ -203,12 +203,14 @@ ActiveRecord::Schema.define(:version =&gt; 20130522141856) do
203 203 create_table "snippets", :force => true do |t|
204 204 t.string "title"
205 205 t.text "content"
206   - t.integer "author_id", :null => false
207   - t.integer "project_id", :null => false
208   - t.datetime "created_at", :null => false
209   - t.datetime "updated_at", :null => false
  206 + t.integer "author_id", :null => false
  207 + t.integer "project_id"
  208 + t.datetime "created_at", :null => false
  209 + t.datetime "updated_at", :null => false
210 210 t.string "file_name"
211 211 t.datetime "expires_at"
  212 + t.boolean "private", :default => true, :null => false
  213 + t.string "type"
212 214 end
213 215  
214 216 add_index "snippets", ["created_at"], :name => "index_snippets_on_created_at"
... ...
features/project/snippets.feature 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +Feature: Project Snippets
  2 + Background:
  3 + Given I sign in as a user
  4 + And I own project "Shop"
  5 + And project "Shop" have "Snippet one" snippet
  6 + And project "Shop" have no "Snippet two" snippet
  7 + And I visit project "Shop" snippets page
  8 +
  9 + Scenario: I should see snippets
  10 + Given I visit project "Shop" snippets page
  11 + Then I should see "Snippet one" in snippets
  12 + And I should not see "Snippet two" in snippets
  13 +
  14 + Scenario: I create new project snippet
  15 + Given I click link "New Snippet"
  16 + And I submit new snippet "Snippet three"
  17 + Then I should see snippet "Snippet three"
  18 +
  19 + @javascript
  20 + Scenario: I comment on a snippet "Snippet one"
  21 + Given I visit snippet page "Snippet one"
  22 + And I leave a comment like "Good snippet!"
  23 + Then I should see comment "Good snippet!"
  24 +
  25 + Scenario: I update "Snippet one"
  26 + Given I visit snippet page "Snippet one"
  27 + And I click link "Edit"
  28 + And I submit new title "Snippet new title"
  29 + Then I should see "Snippet new title"
  30 +
  31 + Scenario: I destroy "Snippet one"
  32 + Given I visit snippet page "Snippet one"
  33 + And I click link "Edit"
  34 + And I click link "Destroy"
  35 + Then I should not see "Snippet one" in snippets
... ...
features/snippets/discover_snippets.feature 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +Feature: Discover Snippets
  2 + Background:
  3 + Given I sign in as a user
  4 + And I have public "Personal snippet one" snippet
  5 + And I have private "Personal snippet private" snippet
  6 +
  7 + Scenario: I should see snippets
  8 + Given I visit snippets page
  9 + Then I should see "Personal snippet one" in snippets
  10 + And I should not see "Personal snippet private" in snippets
... ...
features/snippets/snippets.feature 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +Feature: Snippets Feature
  2 + Background:
  3 + Given I sign in as a user
  4 + And I have public "Personal snippet one" snippet
  5 + And I have private "Personal snippet private" snippet
  6 +
  7 + Scenario: I create new snippet
  8 + Given I visit new snippet page
  9 + And I submit new snippet "Personal snippet three"
  10 + Then I should see snippet "Personal snippet three"
  11 +
  12 + Scenario: I update "Personal snippet one"
  13 + Given I visit snippet page "Personal snippet one"
  14 + And I click link "Edit"
  15 + And I submit new title "Personal snippet new title"
  16 + Then I should see "Personal snippet new title"
  17 +
  18 + Scenario: Set "Personal snippet one" public
  19 + Given I visit snippet page "Personal snippet one"
  20 + And I click link "Edit"
  21 + And I uncheck "Private" checkbox
  22 + Then I should see "Personal snippet one" public
  23 +
  24 + Scenario: I destroy "Personal snippet one"
  25 + Given I visit snippet page "Personal snippet one"
  26 + And I click link "Edit"
  27 + And I click link "Destroy"
  28 + Then I should not see "Personal snippet one" in snippets
... ...
features/snippets/user_snippets.feature 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +Feature: User Snippets
  2 + Background:
  3 + Given I sign in as a user
  4 + And I have public "Personal snippet one" snippet
  5 + And I have private "Personal snippet private" snippet
  6 +
  7 + Scenario: I should see all my snippets
  8 + Given I visit my snippets page
  9 + Then I should see "Personal snippet one" in snippets
  10 + And I should see "Personal snippet private" in snippets
  11 +
  12 + Scenario: I can see only my private snippets
  13 + Given I visit my snippets page
  14 + And I click "Private" filter
  15 + Then I should not see "Personal snippet one" in snippets
  16 + And I should see "Personal snippet private" in snippets
  17 +
  18 + Scenario: I can see only my public snippets
  19 + Given I visit my snippets page
  20 + And I click "Public" filter
  21 + Then I should see "Personal snippet one" in snippets
  22 + And I should not see "Personal snippet private" in snippets
... ...
features/steps/project/project_snippets.rb 0 → 100644
... ... @@ -0,0 +1,100 @@
  1 +class ProjectSnippets < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedProject
  4 + include SharedNote
  5 + include SharedPaths
  6 +
  7 + And 'project "Shop" have "Snippet one" snippet' do
  8 + create(:project_snippet,
  9 + title: "Snippet one",
  10 + content: "Test content",
  11 + file_name: "snippet.rb",
  12 + project: project,
  13 + author: project.users.first)
  14 + end
  15 +
  16 + And 'project "Shop" have no "Snippet two" snippet' do
  17 + create(:snippet,
  18 + title: "Snippet two",
  19 + content: "Test content",
  20 + file_name: "snippet.rb",
  21 + author: project.users.first)
  22 + end
  23 +
  24 + Given 'I click link "New Snippet"' do
  25 + click_link "Add new snippet"
  26 + end
  27 +
  28 + Given 'I click link "Snippet one"' do
  29 + click_link "Snippet one"
  30 + end
  31 +
  32 + Then 'I should see "Snippet one" in snippets' do
  33 + page.should have_content "Snippet one"
  34 + end
  35 +
  36 + And 'I should not see "Snippet two" in snippets' do
  37 + page.should_not have_content "Snippet two"
  38 + end
  39 +
  40 + And 'I should not see "Snippet one" in snippets' do
  41 + page.should_not have_content "Snippet one"
  42 + end
  43 +
  44 + And 'I click link "Edit"' do
  45 + within ".page_title" do
  46 + click_link "Edit"
  47 + end
  48 + end
  49 +
  50 + And 'I click link "Destroy"' do
  51 + click_link "Destroy"
  52 + end
  53 +
  54 + And 'I submit new snippet "Snippet three"' do
  55 + fill_in "project_snippet_title", :with => "Snippet three"
  56 + select "forever", :from => "project_snippet_expires_at"
  57 + fill_in "project_snippet_file_name", :with => "my_snippet.rb"
  58 + within('.file-editor') do
  59 + find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three'
  60 + end
  61 + click_button "Save"
  62 + end
  63 +
  64 + Then 'I should see snippet "Snippet three"' do
  65 + page.should have_content "Snippet three"
  66 + page.should have_content "Content of snippet three"
  67 + end
  68 +
  69 + And 'I submit new title "Snippet new title"' do
  70 + fill_in "project_snippet_title", :with => "Snippet new title"
  71 + click_button "Save"
  72 + end
  73 +
  74 + Then 'I should see "Snippet new title"' do
  75 + page.should have_content "Snippet new title"
  76 + end
  77 +
  78 + And 'I leave a comment like "Good snippet!"' do
  79 + within('.js-main-target-form') do
  80 + fill_in "note_note", with: "Good snippet!"
  81 + click_button "Add Comment"
  82 + end
  83 + end
  84 +
  85 + Then 'I should see comment "Good snippet!"' do
  86 + page.should have_content "Good snippet!"
  87 + end
  88 +
  89 + And 'I visit snippet page "Snippet one"' do
  90 + visit project_snippet_path(project, project_snippet)
  91 + end
  92 +
  93 + def project
  94 + @project ||= Project.find_by_name!("Shop")
  95 + end
  96 +
  97 + def project_snippet
  98 + @project_snippet ||= ProjectSnippet.find_by_title!("Snippet One")
  99 + end
  100 +end
... ...
features/steps/shared/paths.rb
... ... @@ -275,6 +275,22 @@ module SharedPaths
275 275 visit public_root_path
276 276 end
277 277  
  278 + # ----------------------------------------
  279 + # Snippets
  280 + # ----------------------------------------
  281 +
  282 + Given 'I visit project "Shop" snippets page' do
  283 + visit project_snippets_path(project)
  284 + end
  285 +
  286 + Given 'I visit snippets page' do
  287 + visit snippets_path
  288 + end
  289 +
  290 + Given 'I visit new snippet page' do
  291 + visit new_snippet_path
  292 + end
  293 +
278 294 def root_ref
279 295 @project.repository.root_ref
280 296 end
... ...
features/steps/shared/snippet.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +module SharedSnippet
  2 + include Spinach::DSL
  3 +
  4 + And 'I have public "Personal snippet one" snippet' do
  5 + create(:personal_snippet,
  6 + title: "Personal snippet one",
  7 + content: "Test content",
  8 + file_name: "snippet.rb",
  9 + private: false,
  10 + author: current_user)
  11 + end
  12 +
  13 + And 'I have private "Personal snippet private" snippet' do
  14 + create(:personal_snippet,
  15 + title: "Personal snippet private",
  16 + content: "Provate content",
  17 + file_name: "private_snippet.rb",
  18 + private: true,
  19 + author: current_user)
  20 + end
  21 +end
... ...
features/steps/snippets/discover_snippets.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +class DiscoverSnippets < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedPaths
  4 + include SharedSnippet
  5 +
  6 + Then 'I should see "Personal snippet one" in snippets' do
  7 + page.should have_content "Personal snippet one"
  8 + end
  9 +
  10 + And 'I should not see "Personal snippet private" in snippets' do
  11 + page.should_not have_content "Personal snippet private"
  12 + end
  13 +
  14 + def snippet
  15 + @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
  16 + end
  17 +end
... ...
features/steps/snippets/snippets.rb 0 → 100644
... ... @@ -0,0 +1,65 @@
  1 +class SnippetsFeature < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedPaths
  4 + include SharedProject
  5 + include SharedSnippet
  6 +
  7 + Given 'I click link "Personal snippet one"' do
  8 + click_link "Personal snippet one"
  9 + end
  10 +
  11 + And 'I should not see "Personal snippet one" in snippets' do
  12 + page.should_not have_content "Personal snippet one"
  13 + end
  14 +
  15 + And 'I click link "Edit"' do
  16 + within ".page_title" do
  17 + click_link "Edit"
  18 + end
  19 + end
  20 +
  21 + And 'I click link "Destroy"' do
  22 + click_link "Destroy"
  23 + end
  24 +
  25 + And 'I submit new snippet "Personal snippet three"' do
  26 + fill_in "personal_snippet_title", :with => "Personal snippet three"
  27 + select "forever", :from => "personal_snippet_expires_at"
  28 + fill_in "personal_snippet_file_name", :with => "my_snippet.rb"
  29 + within('.file-editor') do
  30 + find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of snippet three'
  31 + end
  32 + click_button "Save"
  33 + end
  34 +
  35 + Then 'I should see snippet "Personal snippet three"' do
  36 + page.should have_content "Personal snippet three"
  37 + page.should have_content "Content of snippet three"
  38 + end
  39 +
  40 + And 'I submit new title "Personal snippet new title"' do
  41 + fill_in "personal_snippet_title", :with => "Personal snippet new title"
  42 + click_button "Save"
  43 + end
  44 +
  45 + Then 'I should see "Personal snippet new title"' do
  46 + page.should have_content "Personal snippet new title"
  47 + end
  48 +
  49 + And 'I uncheck "Private" checkbox' do
  50 + find(:xpath, "//input[@id='personal_snippet_private']").set true
  51 + click_button "Save"
  52 + end
  53 +
  54 + Then 'I should see "Personal snippet one" public' do
  55 + page.should have_no_xpath("//i[@class='public-snippet']")
  56 + end
  57 +
  58 + And 'I visit snippet page "Personal snippet one"' do
  59 + visit snippet_path(snippet)
  60 + end
  61 +
  62 + def snippet
  63 + @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
  64 + end
  65 +end
... ...
features/steps/snippets/user_snippets.rb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +class UserSnippets < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedPaths
  4 + include SharedSnippet
  5 +
  6 + Given 'I visit my snippets page' do
  7 + visit user_snippets_path(current_user)
  8 + end
  9 +
  10 + Then 'I should see "Personal snippet one" in snippets' do
  11 + page.should have_content "Personal snippet one"
  12 + end
  13 +
  14 + And 'I should see "Personal snippet private" in snippets' do
  15 + page.should have_content "Personal snippet private"
  16 + end
  17 +
  18 + Then 'I should not see "Personal snippet one" in snippets' do
  19 + page.should_not have_content "Personal snippet one"
  20 + end
  21 +
  22 + And 'I should not see "Personal snippet private" in snippets' do
  23 + page.should_not have_content "Personal snippet private"
  24 + end
  25 +
  26 + Given 'I click "Public" filter' do
  27 + within('.nav-stacked') do
  28 + click_link "Public"
  29 + end
  30 + end
  31 +
  32 + Given 'I click "Private" filter' do
  33 + within('.nav-stacked') do
  34 + click_link "Private"
  35 + end
  36 + end
  37 +
  38 + def snippet
  39 + @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
  40 + end
  41 +end
... ...
lib/api/projects.rb
... ... @@ -328,7 +328,7 @@ module API
328 328 # Example Request:
329 329 # POST /projects/:id/snippets
330 330 post ":id/snippets" do
331   - authorize! :write_snippet, user_project
  331 + authorize! :write_project_snippet, user_project
332 332 required_attributes! [:title, :file_name, :code]
333 333  
334 334 attrs = attributes_for_keys [:title, :file_name]
... ... @@ -357,7 +357,7 @@ module API
357 357 # PUT /projects/:id/snippets/:snippet_id
358 358 put ":id/snippets/:snippet_id" do
359 359 @snippet = user_project.snippets.find(params[:snippet_id])
360   - authorize! :modify_snippet, @snippet
  360 + authorize! :modify_project_snippet, @snippet
361 361  
362 362 attrs = attributes_for_keys [:title, :file_name]
363 363 attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
... ... @@ -380,7 +380,7 @@ module API
380 380 delete ":id/snippets/:snippet_id" do
381 381 begin
382 382 @snippet = user_project.snippets.find(params[:snippet_id])
383   - authorize! :modify_snippet, user_project
  383 + authorize! :modify_project_snippet, @snippet
384 384 @snippet.destroy
385 385 rescue
386 386 end
... ...
spec/factories.rb
... ... @@ -197,7 +197,7 @@ FactoryGirl.define do
197 197 url
198 198 end
199 199  
200   - factory :snippet do
  200 + factory :project_snippet do
201 201 project
202 202 author
203 203 title
... ... @@ -205,6 +205,20 @@ FactoryGirl.define do
205 205 file_name
206 206 end
207 207  
  208 + factory :personal_snippet do
  209 + author
  210 + title
  211 + content
  212 + file_name
  213 + end
  214 +
  215 + factory :snippet do
  216 + author
  217 + title
  218 + content
  219 + file_name
  220 + end
  221 +
208 222 factory :protected_branch do
209 223 name
210 224 project
... ...
spec/features/snippets_spec.rb
... ... @@ -1,99 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe "Snippets" do
4   - let(:project) { create(:project) }
5   -
6   - before do
7   - login_as :user
8   - project.team << [@user, :developer]
9   - end
10   -
11   - describe "GET /snippets" do
12   - before do
13   - @snippet = create(:snippet,
14   - author: @user,
15   - project: project)
16   -
17   - visit project_snippets_path(project)
18   - end
19   -
20   - subject { page }
21   -
22   - it { should have_content(@snippet.title[0..10]) }
23   - it { should have_content(@snippet.project.name) }
24   -
25   - describe "Destroy" do
26   - before do
27   - # admin access to remove snippet
28   - @user.users_projects.destroy_all
29   - project.team << [@user, :master]
30   - visit edit_project_snippet_path(project, @snippet)
31   - end
32   -
33   - it "should remove entry" do
34   - expect {
35   - click_link "destroy_snippet_#{@snippet.id}"
36   - }.to change { Snippet.count }.by(-1)
37   - end
38   - end
39   - end
40   -
41   - describe "New snippet" do
42   - before do
43   - visit project_snippets_path(project)
44   - click_link "New Snippet"
45   - end
46   -
47   - it "should open new snippet popup" do
48   - page.current_path.should == new_project_snippet_path(project)
49   - end
50   -
51   - describe "fill in", js: true do
52   - before do
53   - fill_in "snippet_title", with: "login function"
54   - fill_in "snippet_file_name", with: "test.rb"
55   - page.execute_script("editor.insert('def login; end');")
56   - end
57   -
58   - it { expect { click_button "Save" }.to change {Snippet.count}.by(1) }
59   -
60   - it "should add new snippet to table" do
61   - click_button "Save"
62   - page.current_path.should == project_snippet_path(project, Snippet.last)
63   - page.should have_content "login function"
64   - page.should have_content "test.rb"
65   - end
66   - end
67   - end
68   -
69   - describe "Edit snippet" do
70   - before do
71   - @snippet = create(:snippet,
72   - author: @user,
73   - project: project)
74   - visit project_snippet_path(project, @snippet)
75   - click_link "Edit Snippet"
76   - end
77   -
78   - it "should open edit page" do
79   - page.current_path.should == edit_project_snippet_path(project, @snippet)
80   - end
81   -
82   - describe "fill in" do
83   - before do
84   - fill_in "snippet_title", with: "login function"
85   - fill_in "snippet_file_name", with: "test.rb"
86   - end
87   -
88   - it { expect { click_button "Save" }.to_not change {Snippet.count} }
89   -
90   - it "should update snippet fields" do
91   - click_button "Save"
92   -
93   - page.current_path.should == project_snippet_path(project, @snippet)
94   - page.should have_content "login function"
95   - page.should have_content "test.rb"
96   - end
97   - end
98   - end
99   -end
spec/helpers/gitlab_markdown_helper_spec.rb
... ... @@ -10,7 +10,7 @@ describe GitlabMarkdownHelper do
10 10 let(:commit) { project.repository.commit }
11 11 let(:issue) { create(:issue, project: project) }
12 12 let(:merge_request) { create(:merge_request, project: project) }
13   - let(:snippet) { create(:snippet, project: project) }
  13 + let(:snippet) { create(:project_snippet, project: project) }
14 14 let(:member) { project.users_projects.where(user_id: user).first }
15 15  
16 16 before do
... ... @@ -190,8 +190,43 @@ describe GitlabMarkdownHelper do
190 190 describe "referencing a snippet" do
191 191 let(:object) { snippet }
192 192 let(:reference) { "$#{snippet.id}" }
  193 + let(:actual) { "Reference to #{reference}" }
  194 + let(:expected) { project_snippet_path(project, object) }
  195 +
  196 + it "should link using a valid id" do
  197 + gfm(actual).should match(expected)
  198 + end
  199 +
  200 + it "should link with adjacent text" do
  201 + # Wrap the reference in parenthesis
  202 + gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
  203 +
  204 + # Append some text to the end of the reference
  205 + gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
  206 + end
  207 +
  208 + it "should keep whitespace intact" do
  209 + actual = "Referenced #{reference} already."
  210 + expected = /Referenced <a.+>[^\s]+<\/a> already/
  211 + gfm(actual).should match(expected)
  212 + end
  213 +
  214 + it "should not link with an invalid id" do
  215 + # Modify the reference string so it's still parsed, but is invalid
  216 + reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
  217 + gfm(actual).should == actual
  218 + end
  219 +
  220 + it "should include a title attribute" do
  221 + title = "Snippet: #{object.title}"
  222 + gfm(actual).should match(/title="#{title}"/)
  223 + end
  224 +
  225 + it "should include standard gfm classes" do
  226 + css = object.class.to_s.underscore
  227 + gfm(actual).should match(/class="\s?gfm gfm-snippet\s?"/)
  228 + end
193 229  
194   - include_examples 'referenced object'
195 230 end
196 231  
197 232 describe "referencing multiple objects" do
... ...
spec/models/project_snippet_spec.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: snippets
  4 +#
  5 +# id :integer not null, primary key
  6 +# title :string(255)
  7 +# content :text
  8 +# author_id :integer not null
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# file_name :string(255)
  13 +# expires_at :datetime
  14 +#
  15 +
  16 +require 'spec_helper'
  17 +
  18 +describe ProjectSnippet do
  19 + describe "Associations" do
  20 + it { should belong_to(:project) }
  21 + end
  22 +
  23 + describe "Mass assignment" do
  24 + it { should_not allow_mass_assignment_of(:project_id) }
  25 + end
  26 +
  27 + describe "Validation" do
  28 + it { should validate_presence_of(:project) }
  29 + end
  30 +end
... ...
spec/models/project_spec.rb
... ... @@ -36,7 +36,7 @@ describe Project do
36 36 it { should have_many(:milestones).dependent(:destroy) }
37 37 it { should have_many(:users_projects).dependent(:destroy) }
38 38 it { should have_many(:notes).dependent(:destroy) }
39   - it { should have_many(:snippets).dependent(:destroy) }
  39 + it { should have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
40 40 it { should have_many(:deploy_keys_projects).dependent(:destroy) }
41 41 it { should have_many(:deploy_keys) }
42 42 it { should have_many(:hooks).dependent(:destroy) }
... ...
spec/models/snippet_spec.rb
... ... @@ -17,19 +17,16 @@ require &#39;spec_helper&#39;
17 17  
18 18 describe Snippet do
19 19 describe "Associations" do
20   - it { should belong_to(:project) }
21 20 it { should belong_to(:author).class_name('User') }
22 21 it { should have_many(:notes).dependent(:destroy) }
23 22 end
24 23  
25 24 describe "Mass assignment" do
26 25 it { should_not allow_mass_assignment_of(:author_id) }
27   - it { should_not allow_mass_assignment_of(:project_id) }
28 26 end
29 27  
30 28 describe "Validation" do
31 29 it { should validate_presence_of(:author) }
32   - it { should validate_presence_of(:project) }
33 30  
34 31 it { should validate_presence_of(:title) }
35 32 it { should ensure_length_of(:title).is_within(0..255) }
... ...
spec/models/user_spec.rb
... ... @@ -41,6 +41,7 @@ require &#39;spec_helper&#39;
41 41 describe User do
42 42 describe "Associations" do
43 43 it { should have_one(:namespace) }
  44 + it { should have_many(:snippets).class_name('Snippet').dependent(:destroy) }
44 45 it { should have_many(:users_projects).dependent(:destroy) }
45 46 it { should have_many(:groups) }
46 47 it { should have_many(:keys).dependent(:destroy) }
... ...
spec/requests/api/notes_spec.rb
... ... @@ -7,7 +7,7 @@ describe API::API do
7 7 let!(:project) { create(:project, namespace: user.namespace ) }
8 8 let!(:issue) { create(:issue, project: project, author: user) }
9 9 let!(:merge_request) { create(:merge_request, project: project, author: user) }
10   - let!(:snippet) { create(:snippet, project: project, author: user) }
  10 + let!(:snippet) { create(:project_snippet, project: project, author: user) }
11 11 let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
12 12 let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
13 13 let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
... ...
spec/requests/api/projects_spec.rb
... ... @@ -10,7 +10,7 @@ describe API::API do
10 10 let(:admin) { create(:admin) }
11 11 let!(:project) { create(:project_with_code, creator_id: user.id) }
12 12 let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
13   - let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') }
  13 + let!(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
14 14 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
15 15 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
16 16  
... ...
spec/routing/project_routing_spec.rb
... ... @@ -258,13 +258,37 @@ end
258 258 # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show
259 259 # PUT /:project_id/snippets/:id(.:format) snippets#update
260 260 # DELETE /:project_id/snippets/:id(.:format) snippets#destroy
261   -describe SnippetsController, "routing" do
  261 +describe Project::SnippetsController, "routing" do
262 262 it "to #raw" do
263   - get("/gitlabhq/snippets/1/raw").should route_to('snippets#raw', project_id: 'gitlabhq', id: '1')
  263 + get("/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlabhq', id: '1')
264 264 end
265 265  
266   - it_behaves_like "RESTful project resources" do
267   - let(:controller) { 'snippets' }
  266 + it "to #index" do
  267 + get("/gitlabhq/snippets").should route_to("projects/snippets#index", project_id: 'gitlabhq')
  268 + end
  269 +
  270 + it "to #create" do
  271 + post("/gitlabhq/snippets").should route_to("projects/snippets#create", project_id: 'gitlabhq')
  272 + end
  273 +
  274 + it "to #new" do
  275 + get("/gitlabhq/snippets/new").should route_to("projects/snippets#new", project_id: 'gitlabhq')
  276 + end
  277 +
  278 + it "to #edit" do
  279 + get("/gitlabhq/snippets/1/edit").should route_to("projects/snippets#edit", project_id: 'gitlabhq', id: '1')
  280 + end
  281 +
  282 + it "to #show" do
  283 + get("/gitlabhq/snippets/1").should route_to("projects/snippets#show", project_id: 'gitlabhq', id: '1')
  284 + end
  285 +
  286 + it "to #update" do
  287 + put("/gitlabhq/snippets/1").should route_to("projects/snippets#update", project_id: 'gitlabhq', id: '1')
  288 + end
  289 +
  290 + it "to #destroy" do
  291 + delete("/gitlabhq/snippets/1").should route_to("projects/snippets#destroy", project_id: 'gitlabhq', id: '1')
268 292 end
269 293 end
270 294  
... ...
spec/routing/routing_spec.rb
... ... @@ -19,6 +19,51 @@ describe &quot;Mounted Apps&quot;, &quot;routing&quot; do
19 19 end
20 20 end
21 21  
  22 +# snippets GET /snippets(.:format) snippets#index
  23 +# POST /snippets(.:format) snippets#create
  24 +# new_snippet GET /snippets/new(.:format) snippets#new
  25 +# edit_snippet GET /snippets/:id/edit(.:format) snippets#edit
  26 +# snippet GET /snippets/:id(.:format) snippets#show
  27 +# PUT /snippets/:id(.:format) snippets#update
  28 +# DELETE /snippets/:id(.:format) snippets#destroy
  29 +describe SnippetsController, "routing" do
  30 + it "to #user_index" do
  31 + get("/s/User").should route_to('snippets#user_index', username: 'User')
  32 + end
  33 +
  34 + it "to #raw" do
  35 + get("/snippets/1/raw").should route_to('snippets#raw', id: '1')
  36 + end
  37 +
  38 + it "to #index" do
  39 + get("/snippets").should route_to('snippets#index')
  40 + end
  41 +
  42 + it "to #create" do
  43 + post("/snippets").should route_to('snippets#create')
  44 + end
  45 +
  46 + it "to #new" do
  47 + get("/snippets/new").should route_to('snippets#new')
  48 + end
  49 +
  50 + it "to #edit" do
  51 + get("/snippets/1/edit").should route_to('snippets#edit', id: '1')
  52 + end
  53 +
  54 + it "to #show" do
  55 + get("/snippets/1").should route_to('snippets#show', id: '1')
  56 + end
  57 +
  58 + it "to #update" do
  59 + put("/snippets/1").should route_to('snippets#update', id: '1')
  60 + end
  61 +
  62 + it "to #destroy" do
  63 + delete("/snippets/1").should route_to('snippets#destroy', id: '1')
  64 + end
  65 +end
  66 +
22 67 # help GET /help(.:format) help#index
23 68 # help_permissions GET /help/permissions(.:format) help#permissions
24 69 # help_workflow GET /help/workflow(.:format) help#workflow
... ...