Commit cb659e4c5a1c32bd2aa5c9994cd9d7b4a9841c9b

Authored by Timm Drevensek
2 parents ea82bcd3 3b0510a7

Merge branch 'master' into request/relative_submodules

Conflicts:
	CHANGELOG
Showing 70 changed files with 873 additions and 552 deletions   Show diff stats
CHANGELOG
1 1 v 6.8.0
2 2 - Ability to at mention users that are participating in issue and merge req. discussion
3 3 - Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu)
4   - - Add support for relative submodules
  4 + - Make user search case-insensitive (Christopher Arnold)
  5 + - Remove omniauth-ldap nickname bug workaround
  6 + - Drop all tables before restoring a Postgres backup
  7 + - Make the repository downloads path configurable
  8 + - Create branches via API (sponsored by O'Reilly Media)
5 9  
6 10 v 6.7.2
7 11 - Fix upgrader script
... ...
CONTRIBUTING.md
... ... @@ -63,10 +63,11 @@ If you can, please submit a merge request with the fix or improvements including
63 63 1. Add your changes to the [CHANGELOG](CHANGELOG)
64 64 1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
65 65 1. Push the commit to your fork
66   -1. Submit a merge request (MR)
  66 +1. Submit a merge request (MR) to the master branch
67 67 1. The MR title should describes the change you want to make
68 68 1. The MR description should give a motive for your change and the method you used to achieve it
69 69 1. If the MR changes the UI it should include before and after screenshots
  70 +1. If the MR changes CSS classes please include the list of affected pages `grep css-class ./app -R`
70 71 1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feature requests](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
71 72 1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion
72 73 1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
... ...
Guardfile
1 1 # A sample Guardfile
2 2 # More info at https://github.com/guard/guard#readme
3 3  
4   -guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do
  4 +guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_pass: false do
5 5 watch(%r{^spec/.+_spec\.rb$})
6 6 watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7 7 watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
... ...
README.md
... ... @@ -113,38 +113,10 @@ or start each component separately
113 113 Single Spinach test: bundle exec spinach features/project/issues/milestones.feature
114 114  
115 115  
116   -### GitLab interfaces
  116 +### Documentation
117 117  
118   -* [GitLab API doc](doc/api/README.md) or see the [GitLab API website](http://api.gitlab.org/)
119   -
120   -* [Rake tasks](doc/raketasks) including a [backup and restore procedure](doc/raketasks/backup_restore.md)
121   -
122   -* [Directory structure](doc/install/structure.md)
123   -
124   -* [Database installation](doc/install/databases.md)
125   -
126   -* [Markdown specification](doc/markdown/markdown.md)
127   -
128   -* [Security guide](doc/security/rack_attack.md) to throttle abusive requests
  118 +All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/).
129 119  
130 120 ### Getting help
131 121  
132   -* [Maintenance policy](MAINTENANCE.md) specifies what versions are supported.
133   -
134   -* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) contains solutions to common problems.
135   -
136   -* [Mailing list](https://groups.google.com/forum/#!forum/gitlabhq) and [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) are the best places to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please search for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and has resolved it. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix.
137   -
138   -* [Feature request forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab.
139   -
140   -* [Contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) describes how to submit merge requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed.
141   -
142   -* [Support subscription](http://www.gitlab.com/subscription/) connects you to the knowledge of GitLab experts that will resolve your issues and answer your questions.
143   -
144   -* [Consultancy](http://www.gitlab.com/consultancy/) from the GitLab experts for installations, upgrades and customizations.
145   -
146   -* [#gitlab IRC channel](http://www.freenode.net/) on Freenode to get in touch with other GitLab users and get help, it's managed by James Newton (newton), Drew Blessing (dblessing), and Sam Gleske (sag47).
147   -
148   -* [Book](http://www.packtpub.com/gitlab-repository-management/book) written by GitLab enthusiast Jonathan M. Hethey is unofficial but it offers a good overview.
149   -
150   -* [Gitter chat room](https://gitter.im/gitlabhq/gitlabhq#) here you can ask questions when you need help.
  122 +Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help.
... ...
app/assets/stylesheets/generic/lists.scss
... ... @@ -13,6 +13,12 @@
13 13 border-bottom: 1px solid #eee;
14 14 border-bottom: 1px solid rgba(0, 0, 0, 0.05);
15 15  
  16 + &:after {
  17 + content: " ";
  18 + display: table;
  19 + clear: both;
  20 + }
  21 +
16 22 &.disabled {
17 23 color: #888;
18 24 }
... ... @@ -46,6 +52,12 @@
46 52  
47 53 .author { color: #999; }
48 54  
  55 + .list-item-name {
  56 + float: left;
  57 + position: relative;
  58 + top: 3px;
  59 + }
  60 +
49 61 p {
50 62 padding-top: 1px;
51 63 margin: 0;
... ...
app/assets/stylesheets/sections/dashboard.scss
... ... @@ -113,6 +113,5 @@
113 113 float: left;
114 114 margin-right: 3px;
115 115 color: #999;
116   - margin-bottom: 10px;
117 116 width: 16px;
118 117 }
... ...
app/assets/stylesheets/sections/diff.scss
... ... @@ -62,6 +62,29 @@
62 62 font-size: 12px;
63 63 }
64 64 }
  65 +
  66 + .text-file-parallel div {
  67 + display: inline-block;
  68 + padding-bottom: 16px;
  69 + }
  70 + .diff-side {
  71 + overflow-x: scroll;
  72 + width: 508px;
  73 + height: 700px;
  74 + }
  75 + .diff-side.diff-side-left{
  76 + overflow-y:hidden;
  77 + }
  78 + .diff-side table, td.diff-middle table {
  79 + height: 700px;
  80 + }
  81 + .diff-middle {
  82 + width: 114px;
  83 + vertical-align: top;
  84 + height: 700px;
  85 + overflow: hidden
  86 + }
  87 +
65 88 .old_line, .new_line, .diff_line {
66 89 margin: 0px;
67 90 padding: 0px;
... ... @@ -125,8 +148,6 @@
125 148 }
126 149 &.parallel {
127 150 display: table-cell;
128   - overflow: hidden;
129   - width: 50%;
130 151 }
131 152 }
132 153 }
... ...
app/controllers/projects/branches_controller.rb
... ... @@ -16,11 +16,7 @@ class Projects::BranchesController < Projects::ApplicationController
16 16 end
17 17  
18 18 def create
19   - @repository.add_branch(params[:branch_name], params[:ref])
20   -
21   - if new_branch = @repository.find_branch(params[:branch_name])
22   - Event.create_ref_event(@project, current_user, new_branch, 'add')
23   - end
  19 + CreateBranchService.new.execute(project, params[:branch_name], params[:ref], current_user)
24 20  
25 21 redirect_to project_branches_path(@project)
26 22 end
... ...
app/controllers/projects/repositories_controller.rb
... ... @@ -14,7 +14,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
14 14 render_404 and return
15 15 end
16 16  
17   - storage_path = Rails.root.join("tmp", "repositories")
  17 + storage_path = Gitlab.config.gitlab.repository_downloads_path
18 18  
19 19 file_path = @repository.archive_repo(params[:ref], storage_path, params[:format].downcase)
20 20  
... ...
app/helpers/commits_helper.rb
... ... @@ -105,8 +105,80 @@ module CommitsHelper
105 105 branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
106 106 end
107 107  
108   - def get_old_file(project, commit, diff)
109   - project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
  108 + def parallel_diff_lines(project, commit, diff, file)
  109 + old_file = project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
  110 + deleted_lines = {}
  111 + added_lines = {}
  112 + each_diff_line(diff, 0) do |line, type, line_code, line_new, line_old|
  113 + if type == "old"
  114 + deleted_lines[line_old] = { line_code: line_code, type: type, line: line }
  115 + elsif type == "new"
  116 + added_lines[line_new] = { line_code: line_code, type: type, line: line }
  117 + end
  118 + end
  119 + max_length = old_file ? old_file.sloc + added_lines.length : file.sloc
  120 +
  121 + offset1 = 0
  122 + offset2 = 0
  123 + old_lines = []
  124 + new_lines = []
  125 +
  126 + max_length.times do |line_index|
  127 + line_index1 = line_index - offset1
  128 + line_index2 = line_index - offset2
  129 + deleted_line = deleted_lines[line_index1 + 1]
  130 + added_line = added_lines[line_index2 + 1]
  131 + old_line = old_file.lines[line_index1] if old_file
  132 + new_line = file.lines[line_index2]
  133 +
  134 + if deleted_line && added_line
  135 + elsif deleted_line
  136 + new_line = nil
  137 + offset2 += 1
  138 + elsif added_line
  139 + old_line = nil
  140 + offset1 += 1
  141 + end
  142 +
  143 + old_lines[line_index] = DiffLine.new
  144 + new_lines[line_index] = DiffLine.new
  145 +
  146 + # old
  147 + if line_index == 0 && diff.new_file
  148 + old_lines[line_index].type = :file_created
  149 + old_lines[line_index].content = 'File was created'
  150 + elsif deleted_line
  151 + old_lines[line_index].type = :deleted
  152 + old_lines[line_index].content = old_line
  153 + old_lines[line_index].num = line_index1 + 1
  154 + old_lines[line_index].code = deleted_line[:line_code]
  155 + elsif old_line
  156 + old_lines[line_index].type = :no_change
  157 + old_lines[line_index].content = old_line
  158 + old_lines[line_index].num = line_index1 + 1
  159 + else
  160 + old_lines[line_index].type = :added
  161 + end
  162 +
  163 + # new
  164 + if line_index == 0 && diff.deleted_file
  165 + new_lines[line_index].type = :file_deleted
  166 + new_lines[line_index].content = "File was deleted"
  167 + elsif added_line
  168 + new_lines[line_index].type = :added
  169 + new_lines[line_index].num = line_index2 + 1
  170 + new_lines[line_index].content = new_line
  171 + new_lines[line_index].code = added_line[:line_code]
  172 + elsif new_line
  173 + new_lines[line_index].type = :no_change
  174 + new_lines[line_index].num = line_index2 + 1
  175 + new_lines[line_index].content = new_line
  176 + else
  177 + new_lines[line_index].type = :deleted
  178 + end
  179 + end
  180 +
  181 + return old_lines, new_lines
110 182 end
111 183  
112 184 protected
... ...
app/models/diff_line.rb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +class DiffLine
  2 + attr_accessor :type, :content, :num, :code
  3 +end
... ...
app/models/user.rb
... ... @@ -204,7 +204,7 @@ class User < ActiveRecord::Base
204 204 end
205 205  
206 206 def search query
207   - where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
  207 + where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
208 208 end
209 209  
210 210 def by_username_or_id(name_or_id)
... ...
app/services/create_branch_service.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class CreateBranchService
  2 + def execute(project, branch_name, ref, current_user)
  3 + repository = project.repository
  4 + repository.add_branch(branch_name, ref)
  5 + new_branch = repository.find_branch(branch_name)
  6 +
  7 + if new_branch
  8 + Event.create_ref_event(project, current_user, new_branch, 'add')
  9 + end
  10 +
  11 + new_branch
  12 + end
  13 +end
... ...
app/services/notification_service.rb
... ... @@ -178,21 +178,41 @@ class NotificationService
178 178  
179 179 # Get project users with WATCH notification level
180 180 def project_watchers(project)
181   - project_watchers = []
182   - member_methods = { project => :users_projects }
183   - member_methods.merge!(project.group => :users_groups) if project.group
184   -
185   - member_methods.each do |object, member_method|
186   - # Get project notification settings since it has higher priority
187   - user_ids = object.send(member_method).where(notification_level: Notification::N_WATCH).pluck(:user_id)
188   - project_watchers += User.where(id: user_ids)
189   -
190   - # next collect users who use global settings with watch state
191   - user_ids = object.send(member_method).where(notification_level: Notification::N_GLOBAL).pluck(:user_id)
192   - project_watchers += User.where(id: user_ids, notification_level: Notification::N_WATCH)
  181 + # Gather all user ids that have WATCH notification setting for project
  182 + project_notification_uids = project_notification_list(project, Notification::N_WATCH)
  183 +
  184 + # Gather all user ids that have WATCH notification setting for group
  185 + group_notification_uids = group_notification_list(project, Notification::N_WATCH)
  186 +
  187 + # Gather all user ids that have GLOBAL setting
  188 + global_notification_uids = global_notification_list(project)
  189 +
  190 + project_and_group_uids = [project_notification_uids, group_notification_uids].flatten.uniq
  191 + group_and_project_watchers = User.where(id: project_and_group_uids)
  192 +
  193 + # Find all users that have WATCH as their GLOBAL setting
  194 + global_watchers = User.where(id: global_notification_uids, notification_level: Notification::N_WATCH)
  195 +
  196 + [group_and_project_watchers, global_watchers].flatten.uniq
  197 + end
  198 +
  199 + def project_notification_list(project, notification_level)
  200 + project.users_projects.where(notification_level: notification_level).pluck(:user_id)
  201 + end
  202 +
  203 + def group_notification_list(project, notification_level)
  204 + if project.group
  205 + project.group.users_groups.where(notification_level: notification_level).pluck(:user_id)
  206 + else
  207 + []
193 208 end
  209 + end
194 210  
195   - project_watchers.uniq
  211 + def global_notification_list(project)
  212 + [
  213 + project_notification_list(project, Notification::N_GLOBAL),
  214 + group_notification_list(project, Notification::N_GLOBAL)
  215 + ].flatten
196 216 end
197 217  
198 218 # Remove users with disabled notifications from array
... ...
app/views/admin/groups/show.html.haml
... ... @@ -70,8 +70,9 @@
70 70 - @group.users_groups.order('group_access DESC').each do |member|
71 71 - user = member.user
72 72 %li{class: dom_class(user)}
73   - %strong
74   - = link_to user.name, admin_user_path(user)
  73 + .list-item-name
  74 + %strong
  75 + = link_to user.name, admin_user_path(user)
75 76 %span.pull-right.light
76 77 = member.human_access
77 78 = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
... ...
app/views/admin/hooks/index.html.haml
... ... @@ -28,8 +28,10 @@
28 28 %ul.well-list
29 29 - @hooks.each do |hook|
30 30 %li
  31 + .list-item-name
  32 + = link_to admin_hook_path(hook) do
  33 + %strong= hook.url
  34 +
31 35 .pull-right
32 36 = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small"
33 37 = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
34   - = link_to admin_hook_path(hook) do
35   - %strong= hook.url
... ...
app/views/admin/projects/index.html.haml
... ... @@ -44,9 +44,10 @@
44 44 %ul.well-list
45 45 - @projects.each do |project|
46 46 %li
47   - %span{ class: visibility_level_color(project.visibility_level) }
48   - = visibility_level_icon(project.visibility_level)
49   - = link_to project.name_with_namespace, [:admin, project]
  47 + .list-item-name
  48 + %span{ class: visibility_level_color(project.visibility_level) }
  49 + = visibility_level_icon(project.visibility_level)
  50 + = link_to project.name_with_namespace, [:admin, project]
50 51 .pull-right
51 52 %span.label.label-gray
52 53 = repository_size(project)
... ...
app/views/admin/projects/show.html.haml
... ... @@ -116,8 +116,9 @@
116 116 - @project.users_projects.each do |users_project|
117 117 - user = users_project.user
118 118 %li.users_project
119   - %strong
120   - = link_to user.name, admin_user_path(user)
  119 + .list-item-name
  120 + %strong
  121 + = link_to user.name, admin_user_path(user)
121 122 .pull-right
122 123 - if users_project.owner?
123 124 %span.light Owner
... ...
app/views/admin/users/index.html.haml
... ... @@ -36,15 +36,16 @@
36 36 %ul.well-list
37 37 - @users.each do |user|
38 38 %li
39   - - if user.blocked?
40   - %i.icon-lock.cred
41   - - else
42   - %i.icon-user.cgreen
43   - = link_to user.name, [:admin, user]
44   - - if user.admin?
45   - %strong.cred (Admin)
46   - - if user == current_user
47   - %span.cred It's you!
  39 + .list-item-name
  40 + - if user.blocked?
  41 + %i.icon-lock.cred
  42 + - else
  43 + %i.icon-user.cgreen
  44 + = link_to user.name, [:admin, user]
  45 + - if user.admin?
  46 + %strong.cred (Admin)
  47 + - if user == current_user
  48 + %span.cred It's you!
48 49 .pull-right
49 50 %span.light
50 51 %i.icon-envelope
... ...
app/views/admin/users/show.html.haml
... ... @@ -124,7 +124,8 @@
124 124 - @user.users_groups.each do |user_group|
125 125 - group = user_group.group
126 126 %li.users_group
127   - %strong= link_to group.name, admin_group_path(group)
  127 + %span{class: ("list-item-name" unless user_group.owner?)}
  128 + %strong= link_to group.name, admin_group_path(group)
128 129 .pull-right
129 130 %span.light= user_group.human_access
130 131 - unless user_group.owner?
... ...
app/views/groups/edit.html.haml
... ... @@ -73,8 +73,9 @@
73 73 %ul.well-list
74 74 - @group.projects.each do |project|
75 75 %li
76   - = visibility_level_icon(project.visibility_level)
77   - = link_to project.name_with_namespace, project
  76 + .list-item-name
  77 + = visibility_level_icon(project.visibility_level)
  78 + = link_to project.name_with_namespace, project
78 79 .pull-right
79 80 = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
80 81 = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
... ...
app/views/layouts/notify.html.haml
... ... @@ -23,4 +23,4 @@
23 23 - if @project
24 24 You're receiving this notification because you are a member of the #{link_to @project.name_with_namespace, project_url(@project)} project team.
25 25 - if @target_url
26   - #{link_to "View in GitLab", @target_url}
  26 + #{link_to "View it on GitLab", @target_url}
... ...
app/views/notify/repository_push_email.html.haml
... ... @@ -7,7 +7,7 @@
7 7 %li
8 8 #{commit.short_id} - #{commit.title}
9 9  
10   -%h4 Diff:
  10 +%h4 Changes:
11 11 - @diffs.each do |diff|
12 12 %li
13 13 %strong
... ... @@ -23,6 +23,6 @@
23 23 %br
24 24  
25 25 - if @compare.timeout
26   - %h5 Huge diff. To prevent performance issues it was hidden
  26 + %h5 To prevent performance issues changes are hidden
27 27 - elsif @compare.commits_over_limit?
28   - %h5 Diff for big amount of commits is disabled
  28 + %h5 Changes are not shown due to large amount of commits
... ...
app/views/notify/repository_push_email.text.haml
... ... @@ -6,7 +6,7 @@ Commits:
6 6 #{commit.short_id} - #{truncate(commit.title, length: 40)}
7 7 \
8 8 \
9   -Diff:
  9 +Changes:
10 10 - @diffs.each do |diff|
11 11 \
12 12 \=====================================
... ... @@ -22,4 +22,4 @@ Diff:
22 22 - if @compare.timeout
23 23 Huge diff. To prevent performance issues it was hidden
24 24 - elsif @compare.commits_over_limit?
25   - Diff for big amount of commits is disabled
  25 + Changes are not shown due to large amount of commits
... ...
app/views/projects/commits/_parallel_view.html.haml
1 1 / Side-by-side diff view
2   -- old_file = get_old_file(project, @commit, diff)
3   -- deleted_lines = {}
4   -- added_lines = {}
5   -- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line|
6   - - if type == "old"
7   - - deleted_lines[line_old] = { line_code: line_code, type: type, line: line }
8   - - elsif type == "new"
9   - - added_lines[line_new] = { line_code: line_code, type: type, line: line }
10   -
11   -- max_length = old_file.sloc + added_lines.length if old_file
12   -- max_length ||= file.sloc
13   -- offset1 = 0
14   -- offset2 = 0
  2 +- old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file)
  3 +- num_lines = old_lines.length
15 4  
16 5 %div.text-file-parallel
17   - %table{ style: "table-layout: fixed;" }
18   - - max_length.times do |line_index|
19   - - line_index1 = line_index - offset1
20   - - line_index2 = line_index - offset2
21   - - deleted_line = deleted_lines[line_index1 + 1]
22   - - added_line = added_lines[line_index2 + 1]
23   - - old_line = old_file.lines[line_index1] if old_file
24   - - new_line = file.lines[line_index2]
  6 + %div.diff-side.diff-side-left
  7 + %table
  8 + - old_lines.each do |line|
  9 +
  10 + %tr.line_holder.parallel
  11 + - if line.type == :file_created
  12 + %td.line_content.parallel= "File was created"
  13 + - elsif line.type == :deleted
  14 + %td.line_content{class: "parallel noteable_line old #{line.code}", "line_code" => line.code }= line.content
  15 + - else line.type == :no_change
  16 + %td.line_content.parallel= line.content
  17 +
  18 + %div.diff-middle
  19 + %table
  20 + - num_lines.times do |index|
  21 + %tr
  22 + - if old_lines[index].type == :deleted
  23 + %td.old_line.old= old_lines[index].num
  24 + - else
  25 + %td.old_line= old_lines[index].num
  26 +
  27 + %td.diff_line=""
25 28  
26   - - if deleted_line && added_line
27   - - elsif deleted_line
28   - - new_line = nil
29   - - offset2 += 1
30   - - elsif added_line
31   - - old_line = nil
32   - - offset1 += 1
  29 + - if new_lines[index].type == :added
  30 + %td.new_line.new= new_lines[index].num
  31 + - else
  32 + %td.new_line= new_lines[index].num
33 33  
34   - %tr.line_holder.parallel
35   - - if line_index == 0 && diff.new_file
36   - %td.line_content.parallel= "File was created"
37   - %td.old_line= ""
38   - - elsif deleted_line
39   - %td.line_content{class: "parallel noteable_line old #{deleted_line[:line_code]}", "line_code" => deleted_line[:line_code] }= old_line
40   - %td.old_line.old
41   - = line_index1 + 1
42   - - if @comments_allowed
43   - =# render "projects/notes/diff_note_link", line_code: deleted_line[:line_code]
44   - - elsif old_line
45   - %td.line_content.parallel= old_line
46   - %td.old_line= line_index1 + 1
47   - - else
48   - %td.line_content.parallel= ""
49   - %td.old_line= ""
  34 + %div.diff-side.diff-side-right
  35 + %table
  36 + - new_lines.each do |line|
50 37  
51   - %td.diff_line= ""
  38 + %tr.line_holder.parallel
  39 + - if line.type == :file_deleted
  40 + %td.line_content.parallel= "File was deleted"
  41 + - elsif line.type == :added
  42 + %td.line_content{class: "parallel noteable_line new #{line.code}", "line_code" => line.code }= line.content
  43 + - else line.type == :no_change
  44 + %td.line_content.parallel= line.content
52 45  
53   - - if diff.deleted_file && line_index == 0
54   - %td.new_line= ""
55   - %td.line_content.parallel= "File was deleted"
56   - - elsif added_line
57   - %td.new_line.new
58   - = line_index2 + 1
59   - - if @comments_allowed
60   - =# render "projects/notes/diff_note_link", line_code: added_line[:line_code]
61   - %td.line_content{class: "parallel noteable_line new #{added_line[:line_code]}", "line_code" => added_line[:line_code] }= new_line
62   - - elsif new_line
63   - %td.new_line= line_index2 + 1
64   - %td.line_content.parallel= new_line
65   - - else
66   - %td.new_line= ""
67   - %td.line_content.parallel= ""
  46 +:javascript
  47 + $('.diff-side-right').on('scroll', function(){
  48 + $('.diff-side-left, .diff-middle').scrollTop($(this).scrollTop());
  49 + $('.diff-side-left').scrollLeft($(this).scrollLeft());
  50 + });
68 51  
69   - - if @reply_allowed
70   - - comments1 = []
71   - - comments2 = []
72   - - comments1 = @line_notes.select { |n| n.line_code == deleted_line[:line_code] }.sort_by(&:created_at) if deleted_line
73   - - comments2 = @line_notes.select { |n| n.line_code == added_line[:line_code] }.sort_by(&:created_at) if added_line
74   - - unless comments1.empty? && comments2.empty?
75   - = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2, line1: deleted_line, line2: added_line
76 52 \ No newline at end of file
  53 + $('.diff-side-left').on('scroll', function(){
  54 + $('.diff-side-right, .diff-middle').scrollTop($(this).scrollTop()); // might never be relevant
  55 + $('.diff-side-right').scrollLeft($(this).scrollLeft());
  56 + });
... ...
app/views/projects/commits/_text_file.html.haml
1 1 - too_big = diff.diff.lines.count > 1000
2 2 - if too_big
3   - %a.supp_diff_link Diff suppressed. Click to show
  3 + %a.supp_diff_link Changes suppressed. Click to show
4 4  
5 5 %table.text-file{class: "#{'hide' if too_big}"}
6 6 - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line|
... ...
app/views/projects/compare/show.html.haml
... ... @@ -18,17 +18,17 @@
18 18 - else
19 19 %ul.well-list= render Commit.decorate(@commits), project: @project
20 20  
21   - %h4 Diff
  21 + %h4 Changes
22 22 - if @diffs.present?
23 23 = render "projects/commits/diffs", diffs: @diffs, project: @project
24 24 - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
25 25 .bs-callout.bs-callout-danger
26 26 %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
27   - %p To preserve performance the line diff is not shown.
  27 + %p To preserve performance the line changes are not shown.
28 28 - elsif @timeout
29 29 .bs-callout.bs-callout-danger
30   - %h4 Diff for this comparison is extremely large.
31   - %p Use command line to browse diff for this comparison.
  30 + %h4 Number of changed files for this comparison is extremely large.
  31 + %p Use command line to browse through changes for this comparison.
32 32  
33 33  
34 34 - else
... ...
app/views/projects/deploy_keys/show.html.haml
... ... @@ -2,7 +2,7 @@
2 2 Deploy key:
3 3 = @key.title
4 4 %small
5   - created at
  5 + created on
6 6 = @key.created_at.stamp("Aug 21, 2011")
7 7 .back-link
8 8 = link_to project_deploy_keys_path(@project) do
... ...
app/views/projects/issues/_form.html.haml
1 1 %div.issue-form-holder
2 2 %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}"
3 3 %hr
4   - - if @repository.contribution_guide && !@issue.persisted?
  4 + - if !@repository.empty? && @repository.contribution_guide && !@issue.persisted?
5 5 - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
6 6 .alert.alert-info.col-sm-10.col-sm-offset-2
7 7 ="Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe
... ...
app/views/projects/merge_requests/_show.html.haml
... ... @@ -20,7 +20,7 @@
20 20 %li.diffs-tab{data: {action: 'diffs'}}
21 21 = link_to diffs_project_merge_request_path(@project, @merge_request) do
22 22 %i.icon-list-alt
23   - Diff
  23 + Changes
24 24  
25 25 - content_for :note_actions do
26 26 - if can?(current_user, :modify_merge_request, @merge_request)
... ...
app/views/projects/merge_requests/show/_diffs.html.haml
... ... @@ -5,7 +5,7 @@
5 5 - else
6 6 .bs-callout.bs-callout-warning
7 7 %h4
8   - Diff for this comparison is extremely large.
  8 + Changes view for this comparison is extremely large.
9 9 %p
10 10 You can
11 11 = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request, format: :diff), class: "vlink"
... ...
app/views/projects/merge_requests/show/_mr_accept.html.haml
... ... @@ -4,7 +4,10 @@
4 4 %strong Archived projects cannot be committed to!
5 5 - else
6 6 .bs-callout
7   - %strong You don't have permission to merge this MR
  7 + .automerge_widget.cannot_be_merged.hide
  8 + %strong This can't be merged automatically, even if it could be merged you don't have the permission to do so.
  9 + .automerge_widget.can_be_merged.hide
  10 + %strong This can be merged automatically but you don't have the permission to do so.
8 11  
9 12  
10 13 - if @show_merge_controls
... ...
app/views/users_groups/_users_group.html.haml
... ... @@ -2,11 +2,12 @@
2 2 - return unless user
3 3 - show_roles = true if show_roles.nil?
4 4 %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
5   - = image_tag avatar_icon(user.email, 16), class: "avatar s16"
6   - %strong= user.name
7   - %span.cgray= user.username
8   - - if user == current_user
9   - %span.label.label-success It's you
  5 + %span{class: ("list-item-name" if show_controls)}
  6 + = image_tag avatar_icon(user.email, 16), class: "avatar s16"
  7 + %strong= user.name
  8 + %span.cgray= user.username
  9 + - if user == current_user
  10 + %span.label.label-success It's you
10 11  
11 12 - if show_roles
12 13 %span.pull-right
... ... @@ -22,7 +23,7 @@
22 23 - else
23 24 = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
24 25 %i.icon-minus.icon-white
25   -
  26 +
26 27 .edit-member.hide.js-toggle-content
27 28 = form_for [@group, member], remote: true do |f|
28 29 .alert.prepend-top-20
... ...
config/gitlab.yml.example
... ... @@ -76,6 +76,11 @@ production: &amp;base
76 76 snippets: false
77 77 visibility_level: "private" # can be "private" | "internal" | "public"
78 78  
  79 + ## Repository downloads directory
  80 + # When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory.
  81 + # The default is 'tmp/repositories' relative to the root of the Rails app.
  82 + # repository_downloads_path: tmp/repositories
  83 +
79 84 ## External issues trackers
80 85 issues_tracker:
81 86 # redmine:
... ...
config/initializers/1_settings.rb
... ... @@ -97,6 +97,7 @@ Settings.gitlab.default_projects_features[&#39;wiki&#39;] = true if Settings.g
97 97 Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil?
98 98 Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
99 99 Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
  100 +Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root)
100 101  
101 102 #
102 103 # Gravatar
... ...
doc/README.md 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +## The GitLab Documentation covers the following subjects
  2 +
  3 ++ [API](api/README.md)
  4 ++ [Development](development/README.md)
  5 ++ [Install](install/README.md)
  6 ++ [Integration](integration/external-issue-tracker.md)
  7 ++ [Legal](legal/README.md)
  8 ++ [Markdown](markdown/markdown.md)
  9 ++ [Permissions](permissions/permissions.md)
  10 ++ [Public access](public_access/public_access.md)
  11 ++ [Raketasks](raketasks/README.md)
  12 ++ [Release](release/README.md)
  13 ++ [Security](security/README.md)
  14 ++ [SSH](ssh/README.md)
  15 ++ [System hooks](system_hooks/system_hooks.md)
  16 ++ [Update](update/README.md)
  17 ++ [Web hooks](web_hooks/web_hooks.md)
  18 ++ [Workflow](workflow/workflow.md)
... ...
doc/api/README.md
1 1 # GitLab API
2 2  
  3 +## End-points
  4 +
  5 ++ [Users](users.md)
  6 ++ [Session](session.md)
  7 ++ [Projects](projects.md)
  8 ++ [Project Snippets](project_snippets.md)
  9 ++ [Repositories](repositories.md)
  10 ++ [Repository Files](repository_files.md)
  11 ++ [Commits](commits.md)
  12 ++ [Branches](branches.md)
  13 ++ [Merge Requests](merge_requests.md)
  14 ++ [Issues](issues.md)
  15 ++ [Milestones](milestones.md)
  16 ++ [Notes](notes.md)
  17 ++ [Deploy Keys](deploy_keys.md)
  18 ++ [System Hooks](system_hooks.md)
  19 ++ [Groups](groups.md)
  20 +
  21 +## Clients
  22 +
  23 ++ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
  24 ++ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
  25 ++ [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
  26 ++ [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
  27 +
  28 +## Introduction
  29 +
3 30 All API requests require authentication. You need to pass a `private_token` parameter by url or header. If passed as header, the header name must be "PRIVATE-TOKEN" (capital and with dash instead of underscore). You can find or reset your private token in your profile.
4 31  
5 32 If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401:
... ... @@ -103,6 +130,10 @@ When listing resources you can pass the following parameters:
103 130 + `page` (default: `1`) - page number
104 131 + `per_page` (default: `20`, max: `100`) - number of items to list per page
105 132  
  133 +[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response.
  134 +These have `rel` prev/next/first/last and contain the relevant url.
  135 +Please use these instead of generating your own urls.
  136 +
106 137 ## id vs iid
107 138  
108 139 When you work with API you may notice two similar fields in api entites: id and iid.
... ... @@ -117,30 +148,3 @@ Issue
117 148  
118 149 So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`
119 150 But when you want to create a link to web page - use `http:://host/project/issues/:iid.json`
120   -
121   -
122   -
123   -## Contents
124   -
125   -+ [Users](users.md)
126   -+ [Session](session.md)
127   -+ [Projects](projects.md)
128   -+ [Project Snippets](project_snippets.md)
129   -+ [Repositories](repositories.md)
130   -+ [Repository Files](repository_files.md)
131   -+ [Commits](commits.md)
132   -+ [Merge Requests](merge_requests.md)
133   -+ [Issues](issues.md)
134   -+ [Milestones](milestones.md)
135   -+ [Notes](notes.md)
136   -+ [Deploy Keys](deploy_keys.md)
137   -+ [System Hooks](system_hooks.md)
138   -+ [Groups](groups.md)
139   -
140   -
141   -## Clients
142   -
143   -+ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
144   -+ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
145   -+ [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
146   -+ [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
... ...
doc/api/branches.md 0 → 100644
... ... @@ -0,0 +1,198 @@
  1 +# Branches
  2 +
  3 +## List repository branches
  4 +
  5 +Get a list of repository branches from a project, sorted by name alphabetically.
  6 +
  7 +```
  8 +GET /projects/:id/repository/branches
  9 +```
  10 +
  11 +Parameters:
  12 +
  13 ++ `id` (required) - The ID of a project
  14 +
  15 +```json
  16 +[
  17 + {
  18 + "name": "master",
  19 + "commit": {
  20 + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
  21 + "parents": [
  22 + {
  23 + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
  24 + }
  25 + ],
  26 + "tree": "46e82de44b1061621357f24c05515327f2795a95",
  27 + "message": "add projects API",
  28 + "author": {
  29 + "name": "John Smith",
  30 + "email": "john@example.com"
  31 + },
  32 + "committer": {
  33 + "name": "John Smith",
  34 + "email": "john@example.com"
  35 + },
  36 + "authored_date": "2012-06-27T05:51:39-07:00",
  37 + "committed_date": "2012-06-28T03:44:20-07:00"
  38 + },
  39 + "protected": true
  40 + }
  41 +]
  42 +```
  43 +
  44 +
  45 +## Get single repository branch
  46 +
  47 +Get a single project repository branch.
  48 +
  49 +```
  50 +GET /projects/:id/repository/branches/:branch
  51 +```
  52 +
  53 +Parameters:
  54 +
  55 ++ `id` (required) - The ID of a project
  56 ++ `branch` (required) - The name of the branch
  57 +
  58 +```json
  59 +{
  60 + "name": "master",
  61 + "commit": {
  62 + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
  63 + "parents": [
  64 + {
  65 + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
  66 + }
  67 + ],
  68 + "tree": "46e82de44b1061621357f24c05515327f2795a95",
  69 + "message": "add projects API",
  70 + "author": {
  71 + "name": "John Smith",
  72 + "email": "john@example.com"
  73 + },
  74 + "committer": {
  75 + "name": "John Smith",
  76 + "email": "john@example.com"
  77 + },
  78 + "authored_date": "2012-06-27T05:51:39-07:00",
  79 + "committed_date": "2012-06-28T03:44:20-07:00"
  80 + },
  81 + "protected": true
  82 +}
  83 +```
  84 +
  85 +
  86 +## Protect repository branch
  87 +
  88 +Protects a single project repository branch. This is an idempotent function, protecting an already
  89 +protected repository branch still returns a `200 Ok` status code.
  90 +
  91 +```
  92 +PUT /projects/:id/repository/branches/:branch/protect
  93 +```
  94 +
  95 +Parameters:
  96 +
  97 ++ `id` (required) - The ID of a project
  98 ++ `branch` (required) - The name of the branch
  99 +
  100 +```json
  101 +{
  102 + "name": "master",
  103 + "commit": {
  104 + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
  105 + "parents": [
  106 + {
  107 + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
  108 + }
  109 + ],
  110 + "tree": "46e82de44b1061621357f24c05515327f2795a95",
  111 + "message": "add projects API",
  112 + "author": {
  113 + "name": "John Smith",
  114 + "email": "john@example.com"
  115 + },
  116 + "committer": {
  117 + "name": "John Smith",
  118 + "email": "john@example.com"
  119 + },
  120 + "authored_date": "2012-06-27T05:51:39-07:00",
  121 + "committed_date": "2012-06-28T03:44:20-07:00"
  122 + },
  123 + "protected": true
  124 +}
  125 +```
  126 +
  127 +
  128 +## Unprotect repository branch
  129 +
  130 +Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
  131 +unprotected repository branch still returns a `200 Ok` status code.
  132 +
  133 +```
  134 +PUT /projects/:id/repository/branches/:branch/unprotect
  135 +```
  136 +
  137 +Parameters:
  138 +
  139 ++ `id` (required) - The ID of a project
  140 ++ `branch` (required) - The name of the branch
  141 +
  142 +```json
  143 +{
  144 + "name": "master",
  145 + "commit": {
  146 + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
  147 + "parents": [
  148 + {
  149 + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
  150 + }
  151 + ],
  152 + "tree": "46e82de44b1061621357f24c05515327f2795a95",
  153 + "message": "add projects API",
  154 + "author": {
  155 + "name": "John Smith",
  156 + "email": "john@example.com"
  157 + },
  158 + "committer": {
  159 + "name": "John Smith",
  160 + "email": "john@example.com"
  161 + },
  162 + "authored_date": "2012-06-27T05:51:39-07:00",
  163 + "committed_date": "2012-06-28T03:44:20-07:00"
  164 + },
  165 + "protected": false
  166 +}
  167 +```
  168 +
  169 +## Create repository branch
  170 +
  171 +
  172 +```
  173 +POST /projects/:id/repository/branches
  174 +```
  175 +
  176 +Parameters:
  177 +
  178 ++ `id` (required) - The ID of a project
  179 ++ `branch_name` (required) - The name of the branch
  180 ++ `ref` (required) - Create branch from commit sha or existing branch
  181 +
  182 +```json
  183 +{
  184 + "name": "my-new-branch",
  185 + "commit": {
  186 + "id": "8848c0e90327a0b70f1865b843fb2fbfb9345e57",
  187 + "message": "Merge pull request #54 from brightbox/use_fog_brightbox_module\n\nUpdate to use fog-brightbox module",
  188 + "parent_ids": ["fff449e0bf453576f16c91d6544f00a2664009d8", "f93a93626fec20fd659f4ed3ab2e64019b6169ae"],
  189 + "authored_date": "2014-02-20T19:54:55+02:00",
  190 + "author_name": "john smith",
  191 + "author_email": "john@example.com",
  192 + "committed_date": "2014-02-20T19:54:55+02:00",
  193 + "committer_name": "john smith",
  194 + "committer_email": "john@example.com"
  195 + },
  196 + "protected": false
  197 +}
  198 +```
... ...
doc/api/repositories.md
1   -## List repository branches
2   -
3   -Get a list of repository branches from a project, sorted by name alphabetically.
4   -
5   -```
6   -GET /projects/:id/repository/branches
7   -```
8   -
9   -Parameters:
10   -
11   -+ `id` (required) - The ID of a project
12   -
13   -```json
14   -[
15   - {
16   - "name": "master",
17   - "commit": {
18   - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
19   - "parents": [
20   - {
21   - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
22   - }
23   - ],
24   - "tree": "46e82de44b1061621357f24c05515327f2795a95",
25   - "message": "add projects API",
26   - "author": {
27   - "name": "John Smith",
28   - "email": "john@example.com"
29   - },
30   - "committer": {
31   - "name": "John Smith",
32   - "email": "john@example.com"
33   - },
34   - "authored_date": "2012-06-27T05:51:39-07:00",
35   - "committed_date": "2012-06-28T03:44:20-07:00"
36   - },
37   - "protected": true
38   - }
39   -]
40   -```
41   -
42   -
43   -## Get single repository branch
44   -
45   -Get a single project repository branch.
46   -
47   -```
48   -GET /projects/:id/repository/branches/:branch
49   -```
50   -
51   -Parameters:
52   -
53   -+ `id` (required) - The ID of a project
54   -+ `branch` (required) - The name of the branch
55   -
56   -```json
57   -{
58   - "name": "master",
59   - "commit": {
60   - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
61   - "parents": [
62   - {
63   - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
64   - }
65   - ],
66   - "tree": "46e82de44b1061621357f24c05515327f2795a95",
67   - "message": "add projects API",
68   - "author": {
69   - "name": "John Smith",
70   - "email": "john@example.com"
71   - },
72   - "committer": {
73   - "name": "John Smith",
74   - "email": "john@example.com"
75   - },
76   - "authored_date": "2012-06-27T05:51:39-07:00",
77   - "committed_date": "2012-06-28T03:44:20-07:00"
78   - },
79   - "protected": true
80   -}
81   -```
82   -
83   -
84   -## Protect repository branch
85   -
86   -Protects a single project repository branch. This is an idempotent function, protecting an already
87   -protected repository branch still returns a `200 Ok` status code.
88   -
89   -```
90   -PUT /projects/:id/repository/branches/:branch/protect
91   -```
92   -
93   -Parameters:
94   -
95   -+ `id` (required) - The ID of a project
96   -+ `branch` (required) - The name of the branch
97   -
98   -```json
99   -{
100   - "name": "master",
101   - "commit": {
102   - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
103   - "parents": [
104   - {
105   - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
106   - }
107   - ],
108   - "tree": "46e82de44b1061621357f24c05515327f2795a95",
109   - "message": "add projects API",
110   - "author": {
111   - "name": "John Smith",
112   - "email": "john@example.com"
113   - },
114   - "committer": {
115   - "name": "John Smith",
116   - "email": "john@example.com"
117   - },
118   - "authored_date": "2012-06-27T05:51:39-07:00",
119   - "committed_date": "2012-06-28T03:44:20-07:00"
120   - },
121   - "protected": true
122   -}
123   -```
124   -
125   -
126   -## Unprotect repository branch
127   -
128   -Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
129   -unprotected repository branch still returns a `200 Ok` status code.
130   -
131   -```
132   -PUT /projects/:id/repository/branches/:branch/unprotect
133   -```
134   -
135   -Parameters:
136   -
137   -+ `id` (required) - The ID of a project
138   -+ `branch` (required) - The name of the branch
139   -
140   -```json
141   -{
142   - "name": "master",
143   - "commit": {
144   - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
145   - "parents": [
146   - {
147   - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
148   - }
149   - ],
150   - "tree": "46e82de44b1061621357f24c05515327f2795a95",
151   - "message": "add projects API",
152   - "author": {
153   - "name": "John Smith",
154   - "email": "john@example.com"
155   - },
156   - "committer": {
157   - "name": "John Smith",
158   - "email": "john@example.com"
159   - },
160   - "authored_date": "2012-06-27T05:51:39-07:00",
161   - "committed_date": "2012-06-28T03:44:20-07:00"
162   - },
163   - "protected": false
164   -}
165   -```
166   -
167   -
168 1 ## List project repository tags
169 2  
170 3 Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
... ...
doc/api/users.md
... ... @@ -51,6 +51,10 @@ GET /users
51 51 ]
52 52 ```
53 53  
  54 +You can search for a users by email or username with:
  55 +`/users?search=John`
  56 +
  57 +Also see `def search query` in `app/models/user.rb`.
54 58  
55 59 ## Single user
56 60  
... ...
doc/development/README.md 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 ++ [Architecture](architecture.md)
  2 ++ [Shell commands](shell_commands.md)
... ...
doc/development/architecture.md
... ... @@ -28,7 +28,7 @@ To serve repositories over SSH there&#39;s an add-on application called gitlab-shell
28 28  
29 29 ## Components
30 30  
31   -![GitLab Diagram Overview](resources/gitlab_diagram_overview.png "GitLab Diagram Overview")
  31 +![GitLab Diagram Overview](resources/gitlab_diagram_overview.png)
32 32  
33 33 A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS.
34 34 It uses Nginx or Apache as a web front end to proxypass the Unicorn web server.
... ... @@ -180,4 +180,4 @@ bundle exec rake gitlab:check RAILS_ENV=production
180 180 ```
181 181  
182 182 Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`.
183 183 -While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL.
  184 +While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL.
184 185 \ No newline at end of file
... ...
doc/install/README.md 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 ++ [Installation](installation.md)
  2 ++ [Requirements](requirements.md)
  3 ++ [Structure](structure.md)
  4 ++ [Database MySQL](database_mysql.md)
... ...
doc/install/database_mysql.md
... ... @@ -6,6 +6,9 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
6 6  
7 7 # Install the database packages
8 8 sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
  9 +
  10 + # Ensure you have MySQL version 5.5.14 or later
  11 + mysql --version
9 12  
10 13 # Pick a database root password (can be anything), type it and press enter
11 14 # Retype the database root password and press enter
... ... @@ -23,6 +26,10 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
23 26 # change $password in the command below to a real password you pick
24 27 mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
25 28  
  29 + # Ensure you can use the InnoDB engine which is necessary to support long indexes.
  30 + # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
  31 + mysql> SET storage_engine=INNODB;
  32 +
26 33 # Create the GitLab production database
27 34 mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
28 35  
... ...
doc/legal/README.md 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 ++ [Corporate contributor license agreement](corporate_contributor_license_agreement.md)
  2 ++ [Individual contributor license agreement](individual_contributor_license_agreement.md)
... ...
doc/permissions/permissions.md
... ... @@ -38,7 +38,7 @@ If a user is a GitLab administrator they receive all permissions.
38 38 |------|-----|--------|---------|------|-----|
39 39 |Browse group|✓|✓|✓|✓|✓|
40 40 |Edit group|||||✓|
41   -|create project in group|||||✓|
  41 +|Create project in group|||||✓|
42 42 |Manage group members|||||✓|
43 43 |Remove group|||||✓|
44 44  
... ...
doc/raketasks/README.md 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 ++ [Backup restore](backup_restore.md)
  2 ++ [Cleanup](cleanup.md)
  3 ++ [Features](features.md)
  4 ++ [Maintenance](maintenance.md)
  5 ++ [User management](user_management.md)
  6 ++ [Web hooks](web_hooks.md)
... ...
doc/release/README.md 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 ++ [Monthly](monthly.md)
  2 ++ [Security](security.md)
... ...
doc/security/README.md 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 ++ [Password length limits](password_length_limits.md)
  2 ++ [Rack attack](rack_attack.md)
... ...
doc/ssh/README.md 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 ++ [Deploy keys](deploy_keys.md)
  2 ++ [SSH](ssh.md)
... ...
doc/ssh/deploy_keys.md
... ... @@ -9,4 +9,4 @@ After this the machine that uses the corresponding private key has read-only acc
9 9  
10 10 You can't add the same deploy key twice with the 'New Deploy Key' option.
11 11 If you want to add the same key to another project please enable it in the list that says 'Deploy keys from projects available to you'.
12   -You need to be the owner of the deploy key to see it in this list.
  12 +All the deploy keys of all the projects you have access to are available. This project access can happen through being a direct member of the project or through a group. See `def accessible_deploy_keys` in `app/models/user.rb` for more information.
... ...
doc/update/6.0-to-6.7.md
... ... @@ -140,3 +140,6 @@ Follow the [`upgrade guide from 5.4 to 6.0`](5.4-to-6.0.md), except for the data
140 140 cd /home/git/gitlab
141 141 sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
142 142 ```
  143 +
  144 +## Login issues after upgrade?
  145 +If running in https mode, be sure to read [Can't Verify csrf token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page)
... ...
doc/update/README.md 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 ++ [The indivual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update)
  2 ++ [Uprader](upgrader.md)
  3 ++ [Ruby](ruby.md)
  4 ++ [Patch versions](patch_versions.md)
  5 ++ [MySQL to PostgreSQL](mysql_to_postgresql.md)
... ...
doc/update/mysql-to-postgresql.md
... ... @@ -1,9 +0,0 @@
1   -# Use the shell commands below to convert a MySQL GitLab database to a PostgreSQL one.
2   -
3   -```
4   -git clone https://github.com/lanyrd/mysql-postgresql-converter.git
5   -cd mysql-postgresql-converter
6   -mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
7   -python db_converter.py databasename.mysql databasename.psql
8   -psql -f databasename.psql -d gitlabhq_production
9   -```
doc/update/mysql_to_postgresql.md 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +# Use the shell commands below to convert a MySQL GitLab database to a PostgreSQL one.
  2 +
  3 +```
  4 +git clone https://github.com/lanyrd/mysql-postgresql-converter.git
  5 +cd mysql-postgresql-converter
  6 +mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
  7 +python db_converter.py databasename.mysql databasename.psql
  8 +psql -f databasename.psql -d gitlabhq_production
  9 +```
... ...
doc/workflow/workflow.md
... ... @@ -3,24 +3,25 @@
3 3 ```bash
4 4 git clone git@example.com:project-name.git
5 5 ```
  6 +
6 7 2. Create branch with your feature
7 8  
8 9 ```bash
9 10 git checkout -b $feature_name
10 11 ```
11 12  
12   -3. Write code. Comit changes
  13 +3. Write code. Commit changes
13 14  
14 15 ```bash
15 16 git commit -am "My feature is ready"
16 17 ```
17 18  
18 19 4. Push your branch to GitLab
19   -
  20 +
20 21 ```bash
21 22 git push origin $feature_name
22 23 ```
23 24  
24   -5. Review your code on Commits page
  25 +5. Review your code on commits page
25 26 6. Create a merge request
26 27 7. Your team lead will review the code &amp; merge it to the main branch
... ...
lib/api/api.rb
... ... @@ -45,5 +45,6 @@ module API
45 45 mount Files
46 46 mount Commits
47 47 mount Namespaces
  48 + mount Branches
48 49 end
49 50 end
... ...
lib/api/branches.rb 0 → 100644
... ... @@ -0,0 +1,85 @@
  1 +require 'mime/types'
  2 +
  3 +module API
  4 + # Projects API
  5 + class Branches < Grape::API
  6 + before { authenticate! }
  7 + before { authorize! :download_code, user_project }
  8 +
  9 + resource :projects do
  10 + # Get a project repository branches
  11 + #
  12 + # Parameters:
  13 + # id (required) - The ID of a project
  14 + # Example Request:
  15 + # GET /projects/:id/repository/branches
  16 + get ":id/repository/branches" do
  17 + present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
  18 + end
  19 +
  20 + # Get a single branch
  21 + #
  22 + # Parameters:
  23 + # id (required) - The ID of a project
  24 + # branch (required) - The name of the branch
  25 + # Example Request:
  26 + # GET /projects/:id/repository/branches/:branch
  27 + get ":id/repository/branches/:branch" do
  28 + @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
  29 + not_found!("Branch does not exist") if @branch.nil?
  30 + present @branch, with: Entities::RepoObject, project: user_project
  31 + end
  32 +
  33 + # Protect a single branch
  34 + #
  35 + # Parameters:
  36 + # id (required) - The ID of a project
  37 + # branch (required) - The name of the branch
  38 + # Example Request:
  39 + # PUT /projects/:id/repository/branches/:branch/protect
  40 + put ":id/repository/branches/:branch/protect" do
  41 + authorize_admin_project
  42 +
  43 + @branch = user_project.repository.find_branch(params[:branch])
  44 + not_found! unless @branch
  45 + protected_branch = user_project.protected_branches.find_by(name: @branch.name)
  46 + user_project.protected_branches.create(name: @branch.name) unless protected_branch
  47 +
  48 + present @branch, with: Entities::RepoObject, project: user_project
  49 + end
  50 +
  51 + # Unprotect a single branch
  52 + #
  53 + # Parameters:
  54 + # id (required) - The ID of a project
  55 + # branch (required) - The name of the branch
  56 + # Example Request:
  57 + # PUT /projects/:id/repository/branches/:branch/unprotect
  58 + put ":id/repository/branches/:branch/unprotect" do
  59 + authorize_admin_project
  60 +
  61 + @branch = user_project.repository.find_branch(params[:branch])
  62 + not_found! unless @branch
  63 + protected_branch = user_project.protected_branches.find_by(name: @branch.name)
  64 + protected_branch.destroy if protected_branch
  65 +
  66 + present @branch, with: Entities::RepoObject, project: user_project
  67 + end
  68 +
  69 + # Create branch
  70 + #
  71 + # Parameters:
  72 + # id (required) - The ID of a project
  73 + # branch_name (required) - The name of the branch
  74 + # ref (required) - Create branch from commit sha or existing branch
  75 + # Example Request:
  76 + # POST /projects/:id/repository/branches
  77 + post ":id/repository/branches" do
  78 + authorize_push_project
  79 + @branch = CreateBranchService.new.execute(user_project, params[:branch_name], params[:ref], current_user)
  80 +
  81 + present @branch, with: Entities::RepoObject, project: user_project
  82 + end
  83 + end
  84 + end
  85 +end
... ...
lib/api/commits.rb
1 1 require 'mime/types'
2 2  
3 3 module API
4   - # Projects API
  4 + # Projects commits API
5 5 class Commits < Grape::API
6 6 before { authenticate! }
7 7 before { authorize! :download_code, user_project }
8 8  
9 9 resource :projects do
10   - helpers do
11   - def handle_project_member_errors(errors)
12   - if errors[:project_access].any?
13   - error!(errors[:project_access], 422)
14   - end
15   - not_found!
16   - end
17   - end
18   -
19 10 # Get a project repository commits
20 11 #
21 12 # Parameters:
... ...
lib/api/helpers.rb
... ... @@ -56,8 +56,12 @@ module API
56 56 end
57 57 end
58 58  
59   - def paginate(object)
60   - object.page(params[:page]).per(params[:per_page].to_i)
  59 + def paginate(relation)
  60 + per_page = params[:per_page].to_i
  61 + paginated = relation.page(params[:page]).per(per_page)
  62 + add_pagination_headers(paginated, per_page)
  63 +
  64 + paginated
61 65 end
62 66  
63 67 def authenticate!
... ... @@ -74,6 +78,10 @@ module API
74 78 end
75 79 end
76 80  
  81 + def authorize_push_project
  82 + authorize! :push_code, user_project
  83 + end
  84 +
77 85 def authorize_admin_project
78 86 authorize! :admin_project, user_project
79 87 end
... ... @@ -134,6 +142,18 @@ module API
134 142  
135 143 private
136 144  
  145 + def add_pagination_headers(paginated, per_page)
  146 + request_url = request.url.split('?').first
  147 +
  148 + links = []
  149 + links << %(<#{request_url}?page=#{paginated.current_page - 1}&per_page=#{per_page}>; rel="prev") unless paginated.first_page?
  150 + links << %(<#{request_url}?page=#{paginated.current_page + 1}&per_page=#{per_page}>; rel="next") unless paginated.last_page?
  151 + links << %(<#{request_url}?page=1&per_page=#{per_page}>; rel="first")
  152 + links << %(<#{request_url}?page=#{paginated.total_pages}&per_page=#{per_page}>; rel="last")
  153 +
  154 + header 'Link', links.join(', ')
  155 + end
  156 +
137 157 def abilities
138 158 @abilities ||= begin
139 159 abilities = Six.new
... ...
lib/api/repositories.rb
... ... @@ -15,66 +15,6 @@ module API
15 15 not_found!
16 16 end
17 17 end
18   -
19   - # Get a project repository branches
20   - #
21   - # Parameters:
22   - # id (required) - The ID of a project
23   - # Example Request:
24   - # GET /projects/:id/repository/branches
25   - get ":id/repository/branches" do
26   - present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
27   - end
28   -
29   - # Get a single branch
30   - #
31   - # Parameters:
32   - # id (required) - The ID of a project
33   - # branch (required) - The name of the branch
34   - # Example Request:
35   - # GET /projects/:id/repository/branches/:branch
36   - get ":id/repository/branches/:branch" do
37   - @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
38   - not_found!("Branch does not exist") if @branch.nil?
39   - present @branch, with: Entities::RepoObject, project: user_project
40   - end
41   -
42   - # Protect a single branch
43   - #
44   - # Parameters:
45   - # id (required) - The ID of a project
46   - # branch (required) - The name of the branch
47   - # Example Request:
48   - # PUT /projects/:id/repository/branches/:branch/protect
49   - put ":id/repository/branches/:branch/protect" do
50   - authorize_admin_project
51   -
52   - @branch = user_project.repository.find_branch(params[:branch])
53   - not_found! unless @branch
54   - protected_branch = user_project.protected_branches.find_by(name: @branch.name)
55   - user_project.protected_branches.create(name: @branch.name) unless protected_branch
56   -
57   - present @branch, with: Entities::RepoObject, project: user_project
58   - end
59   -
60   - # Unprotect a single branch
61   - #
62   - # Parameters:
63   - # id (required) - The ID of a project
64   - # branch (required) - The name of the branch
65   - # Example Request:
66   - # PUT /projects/:id/repository/branches/:branch/unprotect
67   - put ":id/repository/branches/:branch/unprotect" do
68   - authorize_admin_project
69   -
70   - @branch = user_project.repository.find_branch(params[:branch])
71   - not_found! unless @branch
72   - protected_branch = user_project.protected_branches.find_by(name: @branch.name)
73   - protected_branch.destroy if protected_branch
74   -
75   - present @branch, with: Entities::RepoObject, project: user_project
76   - end
77   -
78 18 # Get a project repository tags
79 19 #
80 20 # Parameters:
... ... @@ -161,7 +101,7 @@ module API
161 101 repo = user_project.repository
162 102 ref = params[:sha]
163 103 format = params[:format]
164   - storage_path = Rails.root.join("tmp", "repositories")
  104 + storage_path = Gitlab.config.gitlab.repository_downloads_path
165 105  
166 106 file_path = repo.archive_repo(ref, storage_path, format)
167 107 if file_path && File.exists?(file_path)
... ...
lib/backup/database.rb
... ... @@ -29,9 +29,10 @@ module Backup
29 29 print "Restoring MySQL database #{config['database']} ... "
30 30 system('mysql', *mysql_args, config['database'], in: db_file_name)
31 31 when "postgresql" then
32   - puts "Destructively rebuilding database schema for RAILS_ENV #{Rails.env}"
33   - Rake::Task["db:schema:load"].invoke
34 32 print "Restoring PostgreSQL database #{config['database']} ... "
  33 + # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
  34 + # statements like MySQL.
  35 + Rake::Task["gitlab:db:drop_all_tables"].invoke
35 36 pg_env
36 37 system('psql', config['database'], '-f', db_file_name)
37 38 end
... ...
lib/gitlab/ldap/user.rb
... ... @@ -81,16 +81,17 @@ module Gitlab
81 81  
82 82 private
83 83  
84   - def find_by_uid(uid)
85   - model.where(provider: provider, extern_uid: uid).last
  84 + def find_by_uid_and_provider
  85 + find_by_uid(uid)
86 86 end
87 87  
88   - def username
89   - (auth.info.nickname || samaccountname).to_s.force_encoding("utf-8")
  88 + def find_by_uid(uid)
  89 + # LDAP distinguished name is case-insensitive
  90 + model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last
90 91 end
91 92  
92   - def samaccountname
93   - (auth.extra[:raw_info][:samaccountname] || []).first
  93 + def username
  94 + auth.info.nickname.to_s.force_encoding("utf-8")
94 95 end
95 96  
96 97 def provider
... ...
lib/support/init.d/gitlab
... ... @@ -149,7 +149,7 @@ exit_if_not_running(){
149 149 }
150 150  
151 151 ## Starts Unicorn and Sidekiq if they're not running.
152   -start() {
  152 +start_gitlab() {
153 153 check_stale_pids
154 154  
155 155 if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then
... ... @@ -167,7 +167,7 @@ start() {
167 167 # Remove old socket if it exists
168 168 rm -f "$socket_path"/gitlab.socket 2>/dev/null
169 169 # Start the web server
170   - RAILS_ENV=$RAILS_ENV script/web start &
  170 + RAILS_ENV=$RAILS_ENV script/web start
171 171 fi
172 172  
173 173 # If sidekiq is already running, don't start it again.
... ... @@ -184,7 +184,7 @@ start() {
184 184 }
185 185  
186 186 ## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them.
187   -stop() {
  187 +stop_gitlab() {
188 188 exit_if_not_running
189 189  
190 190 if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then
... ... @@ -246,7 +246,7 @@ print_status() {
246 246 }
247 247  
248 248 ## Tells unicorn to reload it's config and Sidekiq to restart
249   -reload(){
  249 +reload_gitlab(){
250 250 exit_if_not_running
251 251 if [ "$wpid" = "0" ];then
252 252 echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded."
... ... @@ -263,12 +263,12 @@ reload(){
263 263 }
264 264  
265 265 ## Restarts Sidekiq and Unicorn.
266   -restart(){
  266 +restart_gitlab(){
267 267 check_status
268 268 if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
269   - stop
  269 + stop_gitlab
270 270 fi
271   - start
  271 + start_gitlab
272 272 }
273 273  
274 274  
... ... @@ -276,16 +276,16 @@ restart(){
276 276  
277 277 case "$1" in
278 278 start)
279   - start
  279 + start_gitlab
280 280 ;;
281 281 stop)
282   - stop
  282 + stop_gitlab
283 283 ;;
284 284 restart)
285   - restart
  285 + restart_gitlab
286 286 ;;
287 287 reload|force-reload)
288   - reload
  288 + reload_gitlab
289 289 ;;
290 290 status)
291 291 print_status
... ...
lib/tasks/gitlab/check.rake
... ... @@ -677,7 +677,20 @@ namespace :gitlab do
677 677 end
678 678  
679 679 def filter
680   - Net::LDAP::Filter.present?(ldap_config.uid)
  680 + uid_filter = Net::LDAP::Filter.present?(ldap_config.uid)
  681 + if user_filter
  682 + Net::LDAP::Filter.join(uid_filter, user_filter)
  683 + else
  684 + uid_filter
  685 + end
  686 + end
  687 +
  688 + def user_filter
  689 + if ldap_config['user_filter'] && ldap_config.user_filter.present?
  690 + Net::LDAP::Filter.construct(ldap_config.user_filter)
  691 + else
  692 + nil
  693 + end
681 694 end
682 695  
683 696 def ldap
... ...
lib/tasks/gitlab/db/drop_all_tables.rake 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +namespace :gitlab do
  2 + namespace :db do
  3 + task drop_all_tables: :environment do
  4 + connection = ActiveRecord::Base.connection
  5 + connection.tables.each do |table|
  6 + connection.drop_table(table)
  7 + end
  8 + end
  9 + end
  10 +end
... ...
spec/models/user_spec.rb
... ... @@ -292,6 +292,20 @@ describe User do
292 292 end
293 293 end
294 294  
  295 + describe 'search' do
  296 + let(:user1) { create(:user, username: 'James', email: 'james@testing.com') }
  297 + let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') }
  298 +
  299 + it "should be case insensitive" do
  300 + User.search(user1.username.upcase).to_a.should == [user1]
  301 + User.search(user1.username.downcase).to_a.should == [user1]
  302 + User.search(user2.username.upcase).to_a.should == [user2]
  303 + User.search(user2.username.downcase).to_a.should == [user2]
  304 + User.search(user1.username.downcase).to_a.count.should == 2
  305 + User.search(user2.username.downcase).to_a.count.should == 1
  306 + end
  307 + end
  308 +
295 309 describe 'by_username_or_id' do
296 310 let(:user1) { create(:user, username: 'foo') }
297 311  
... ...
spec/requests/api/branches_spec.rb 0 → 100644
... ... @@ -0,0 +1,115 @@
  1 +require 'spec_helper'
  2 +require 'mime/types'
  3 +
  4 +describe API::API do
  5 + include ApiHelpers
  6 + before(:each) { enable_observers }
  7 + after(:each) {disable_observers}
  8 +
  9 + let(:user) { create(:user) }
  10 + let(:user2) { create(:user) }
  11 + let!(:project) { create(:project, creator_id: user.id) }
  12 + let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
  13 + let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) }
  14 +
  15 + describe "GET /projects/:id/repository/branches" do
  16 + it "should return an array of project branches" do
  17 + get api("/projects/#{project.id}/repository/branches", user)
  18 + response.status.should == 200
  19 + json_response.should be_an Array
  20 + json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
  21 + end
  22 + end
  23 +
  24 + describe "GET /projects/:id/repository/branches/:branch" do
  25 + it "should return the branch information for a single branch" do
  26 + get api("/projects/#{project.id}/repository/branches/new_design", user)
  27 + response.status.should == 200
  28 +
  29 + json_response['name'].should == 'new_design'
  30 + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
  31 + json_response['protected'].should == false
  32 + end
  33 +
  34 + it "should return a 403 error if guest" do
  35 + get api("/projects/#{project.id}/repository/branches", user2)
  36 + response.status.should == 403
  37 + end
  38 +
  39 + it "should return a 404 error if branch is not available" do
  40 + get api("/projects/#{project.id}/repository/branches/unknown", user)
  41 + response.status.should == 404
  42 + end
  43 + end
  44 +
  45 + describe "PUT /projects/:id/repository/branches/:branch/protect" do
  46 + it "should protect a single branch" do
  47 + put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
  48 + response.status.should == 200
  49 +
  50 + json_response['name'].should == 'new_design'
  51 + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
  52 + json_response['protected'].should == true
  53 + end
  54 +
  55 + it "should return a 404 error if branch not found" do
  56 + put api("/projects/#{project.id}/repository/branches/unknown/protect", user)
  57 + response.status.should == 404
  58 + end
  59 +
  60 + it "should return a 403 error if guest" do
  61 + put api("/projects/#{project.id}/repository/branches/new_design/protect", user2)
  62 + response.status.should == 403
  63 + end
  64 +
  65 + it "should return success when protect branch again" do
  66 + put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
  67 + put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
  68 + response.status.should == 200
  69 + end
  70 + end
  71 +
  72 + describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
  73 + it "should unprotect a single branch" do
  74 + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
  75 + response.status.should == 200
  76 +
  77 + json_response['name'].should == 'new_design'
  78 + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
  79 + json_response['protected'].should == false
  80 + end
  81 +
  82 + it "should return success when unprotect branch" do
  83 + put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
  84 + response.status.should == 404
  85 + end
  86 +
  87 + it "should return success when unprotect branch again" do
  88 + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
  89 + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
  90 + response.status.should == 200
  91 + end
  92 + end
  93 +
  94 +
  95 + describe "POST /projects/:id/repository/branches" do
  96 + it "should create a new branch" do
  97 + post api("/projects/#{project.id}/repository/branches", user),
  98 + branch_name: 'new_design',
  99 + ref: '621491c677087aa243f165eab467bfdfbee00be1'
  100 +
  101 + response.status.should == 201
  102 +
  103 + json_response['name'].should == 'new_design'
  104 + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
  105 + end
  106 +
  107 + it "should deny for user without push access" do
  108 + post api("/projects/#{project.id}/repository/branches", user2),
  109 + branch_name: 'new_design',
  110 + ref: '621491c677087aa243f165eab467bfdfbee00be1'
  111 +
  112 + response.status.should == 403
  113 + end
  114 + end
  115 +end
... ...
spec/requests/api/issues_spec.rb
... ... @@ -25,6 +25,12 @@ describe API::API do
25 25 json_response.should be_an Array
26 26 json_response.first['title'].should == issue.title
27 27 end
  28 +
  29 + it "should add pagination headers" do
  30 + get api("/issues?per_page=3", user)
  31 + response.headers['Link'].should ==
  32 + '<http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="last"'
  33 + end
28 34 end
29 35 end
30 36  
... ...
spec/requests/api/repositories_spec.rb
... ... @@ -14,86 +14,6 @@ describe API::API do
14 14  
15 15 before { project.team << [user, :reporter] }
16 16  
17   -
18   - describe "GET /projects/:id/repository/branches" do
19   - it "should return an array of project branches" do
20   - get api("/projects/#{project.id}/repository/branches", user)
21   - response.status.should == 200
22   - json_response.should be_an Array
23   - json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
24   - end
25   - end
26   -
27   - describe "GET /projects/:id/repository/branches/:branch" do
28   - it "should return the branch information for a single branch" do
29   - get api("/projects/#{project.id}/repository/branches/new_design", user)
30   - response.status.should == 200
31   -
32   - json_response['name'].should == 'new_design'
33   - json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
34   - json_response['protected'].should == false
35   - end
36   -
37   - it "should return a 403 error if guest" do
38   - get api("/projects/#{project.id}/repository/branches", user2)
39   - response.status.should == 403
40   - end
41   -
42   - it "should return a 404 error if branch is not available" do
43   - get api("/projects/#{project.id}/repository/branches/unknown", user)
44   - response.status.should == 404
45   - end
46   - end
47   -
48   - describe "PUT /projects/:id/repository/branches/:branch/protect" do
49   - it "should protect a single branch" do
50   - put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
51   - response.status.should == 200
52   -
53   - json_response['name'].should == 'new_design'
54   - json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
55   - json_response['protected'].should == true
56   - end
57   -
58   - it "should return a 404 error if branch not found" do
59   - put api("/projects/#{project.id}/repository/branches/unknown/protect", user)
60   - response.status.should == 404
61   - end
62   -
63   - it "should return a 403 error if guest" do
64   - put api("/projects/#{project.id}/repository/branches/new_design/protect", user2)
65   - response.status.should == 403
66   - end
67   -
68   - it "should return success when protect branch again" do
69   - put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
70   - put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
71   - response.status.should == 200
72   - end
73   - end
74   -
75   - describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
76   - it "should unprotect a single branch" do
77   - put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
78   - response.status.should == 200
79   -
80   - json_response['name'].should == 'new_design'
81   - json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
82   - json_response['protected'].should == false
83   - end
84   -
85   - it "should return success when unprotect branch" do
86   - put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
87   - response.status.should == 404
88   - end
89   -
90   - it "should return success when unprotect branch again" do
91   - put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
92   - put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
93   - response.status.should == 200
94   - end
95   - end
96   -
97 17 describe "GET /projects/:id/repository/tags" do
98 18 it "should return an array of project tags" do
99 19 get api("/projects/#{project.id}/repository/tags", user)
... ...