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
1 v 6.8.0 1 v 6.8.0
2 - Ability to at mention users that are participating in issue and merge req. discussion 2 - Ability to at mention users that are participating in issue and merge req. discussion
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) 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 v 6.7.2 10 v 6.7.2
7 - Fix upgrader script 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,10 +63,11 @@ If you can, please submit a merge request with the fix or improvements including
63 1. Add your changes to the [CHANGELOG](CHANGELOG) 63 1. Add your changes to the [CHANGELOG](CHANGELOG)
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) 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 1. Push the commit to your fork 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 1. The MR title should describes the change you want to make 67 1. The MR title should describes the change you want to make
68 1. The MR description should give a motive for your change and the method you used to achieve it 68 1. The MR description should give a motive for your change and the method you used to achieve it
69 1. If the MR changes the UI it should include before and after screenshots 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 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 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 1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion 72 1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion
72 1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md). 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).
1 # A sample Guardfile 1 # A sample Guardfile
2 # More info at https://github.com/guard/guard#readme 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 watch(%r{^spec/.+_spec\.rb$}) 5 watch(%r{^spec/.+_spec\.rb$})
6 watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 6 watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7 watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" } 7 watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
@@ -113,38 +113,10 @@ or start each component separately @@ -113,38 +113,10 @@ or start each component separately
113 Single Spinach test: bundle exec spinach features/project/issues/milestones.feature 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 ### Getting help 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,6 +13,12 @@
13 border-bottom: 1px solid #eee; 13 border-bottom: 1px solid #eee;
14 border-bottom: 1px solid rgba(0, 0, 0, 0.05); 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 &.disabled { 22 &.disabled {
17 color: #888; 23 color: #888;
18 } 24 }
@@ -46,6 +52,12 @@ @@ -46,6 +52,12 @@
46 52
47 .author { color: #999; } 53 .author { color: #999; }
48 54
  55 + .list-item-name {
  56 + float: left;
  57 + position: relative;
  58 + top: 3px;
  59 + }
  60 +
49 p { 61 p {
50 padding-top: 1px; 62 padding-top: 1px;
51 margin: 0; 63 margin: 0;
app/assets/stylesheets/sections/dashboard.scss
@@ -113,6 +113,5 @@ @@ -113,6 +113,5 @@
113 float: left; 113 float: left;
114 margin-right: 3px; 114 margin-right: 3px;
115 color: #999; 115 color: #999;
116 - margin-bottom: 10px;  
117 width: 16px; 116 width: 16px;
118 } 117 }
app/assets/stylesheets/sections/diff.scss
@@ -62,6 +62,29 @@ @@ -62,6 +62,29 @@
62 font-size: 12px; 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 .old_line, .new_line, .diff_line { 88 .old_line, .new_line, .diff_line {
66 margin: 0px; 89 margin: 0px;
67 padding: 0px; 90 padding: 0px;
@@ -125,8 +148,6 @@ @@ -125,8 +148,6 @@
125 } 148 }
126 &.parallel { 149 &.parallel {
127 display: table-cell; 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,11 +16,7 @@ class Projects::BranchesController < Projects::ApplicationController
16 end 16 end
17 17
18 def create 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 redirect_to project_branches_path(@project) 21 redirect_to project_branches_path(@project)
26 end 22 end
app/controllers/projects/repositories_controller.rb
@@ -14,7 +14,7 @@ class Projects::RepositoriesController < Projects::ApplicationController @@ -14,7 +14,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
14 render_404 and return 14 render_404 and return
15 end 15 end
16 16
17 - storage_path = Rails.root.join("tmp", "repositories") 17 + storage_path = Gitlab.config.gitlab.repository_downloads_path
18 18
19 file_path = @repository.archive_repo(params[:ref], storage_path, params[:format].downcase) 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,8 +105,80 @@ module CommitsHelper
105 branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe 105 branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
106 end 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 end 182 end
111 183
112 protected 184 protected
app/models/diff_line.rb 0 → 100644
@@ -0,0 +1,3 @@ @@ -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,7 +204,7 @@ class User < ActiveRecord::Base
204 end 204 end
205 205
206 def search query 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 end 208 end
209 209
210 def by_username_or_id(name_or_id) 210 def by_username_or_id(name_or_id)
app/services/create_branch_service.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -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,21 +178,41 @@ class NotificationService
178 178
179 # Get project users with WATCH notification level 179 # Get project users with WATCH notification level
180 def project_watchers(project) 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 end 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 end 216 end
197 217
198 # Remove users with disabled notifications from array 218 # Remove users with disabled notifications from array
app/views/admin/groups/show.html.haml
@@ -70,8 +70,9 @@ @@ -70,8 +70,9 @@
70 - @group.users_groups.order('group_access DESC').each do |member| 70 - @group.users_groups.order('group_access DESC').each do |member|
71 - user = member.user 71 - user = member.user
72 %li{class: dom_class(user)} 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 %span.pull-right.light 76 %span.pull-right.light
76 = member.human_access 77 = member.human_access
77 = 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 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,8 +28,10 @@
28 %ul.well-list 28 %ul.well-list
29 - @hooks.each do |hook| 29 - @hooks.each do |hook|
30 %li 30 %li
  31 + .list-item-name
  32 + = link_to admin_hook_path(hook) do
  33 + %strong= hook.url
  34 +
31 .pull-right 35 .pull-right
32 = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small" 36 = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small"
33 = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" 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,9 +44,10 @@
44 %ul.well-list 44 %ul.well-list
45 - @projects.each do |project| 45 - @projects.each do |project|
46 %li 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 .pull-right 51 .pull-right
51 %span.label.label-gray 52 %span.label.label-gray
52 = repository_size(project) 53 = repository_size(project)
app/views/admin/projects/show.html.haml
@@ -116,8 +116,9 @@ @@ -116,8 +116,9 @@
116 - @project.users_projects.each do |users_project| 116 - @project.users_projects.each do |users_project|
117 - user = users_project.user 117 - user = users_project.user
118 %li.users_project 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 .pull-right 122 .pull-right
122 - if users_project.owner? 123 - if users_project.owner?
123 %span.light Owner 124 %span.light Owner
app/views/admin/users/index.html.haml
@@ -36,15 +36,16 @@ @@ -36,15 +36,16 @@
36 %ul.well-list 36 %ul.well-list
37 - @users.each do |user| 37 - @users.each do |user|
38 %li 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 .pull-right 49 .pull-right
49 %span.light 50 %span.light
50 %i.icon-envelope 51 %i.icon-envelope
app/views/admin/users/show.html.haml
@@ -124,7 +124,8 @@ @@ -124,7 +124,8 @@
124 - @user.users_groups.each do |user_group| 124 - @user.users_groups.each do |user_group|
125 - group = user_group.group 125 - group = user_group.group
126 %li.users_group 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 .pull-right 129 .pull-right
129 %span.light= user_group.human_access 130 %span.light= user_group.human_access
130 - unless user_group.owner? 131 - unless user_group.owner?
app/views/groups/edit.html.haml
@@ -73,8 +73,9 @@ @@ -73,8 +73,9 @@
73 %ul.well-list 73 %ul.well-list
74 - @group.projects.each do |project| 74 - @group.projects.each do |project|
75 %li 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 .pull-right 79 .pull-right
79 = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" 80 = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
80 = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" 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,4 +23,4 @@
23 - if @project 23 - if @project
24 You're receiving this notification because you are a member of the #{link_to @project.name_with_namespace, project_url(@project)} project team. 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 - if @target_url 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 +7,7 @@
7 %li 7 %li
8 #{commit.short_id} - #{commit.title} 8 #{commit.short_id} - #{commit.title}
9 9
10 -%h4 Diff: 10 +%h4 Changes:
11 - @diffs.each do |diff| 11 - @diffs.each do |diff|
12 %li 12 %li
13 %strong 13 %strong
@@ -23,6 +23,6 @@ @@ -23,6 +23,6 @@
23 %br 23 %br
24 24
25 - if @compare.timeout 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 - elsif @compare.commits_over_limit? 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,7 +6,7 @@ Commits:
6 #{commit.short_id} - #{truncate(commit.title, length: 40)} 6 #{commit.short_id} - #{truncate(commit.title, length: 40)}
7 \ 7 \
8 \ 8 \
9 -Diff: 9 +Changes:
10 - @diffs.each do |diff| 10 - @diffs.each do |diff|
11 \ 11 \
12 \===================================== 12 \=====================================
@@ -22,4 +22,4 @@ Diff: @@ -22,4 +22,4 @@ Diff:
22 - if @compare.timeout 22 - if @compare.timeout
23 Huge diff. To prevent performance issues it was hidden 23 Huge diff. To prevent performance issues it was hidden
24 - elsif @compare.commits_over_limit? 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 / Side-by-side diff view 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 %div.text-file-parallel 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 \ No newline at end of file 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 - too_big = diff.diff.lines.count > 1000 1 - too_big = diff.diff.lines.count > 1000
2 - if too_big 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 %table.text-file{class: "#{'hide' if too_big}"} 5 %table.text-file{class: "#{'hide' if too_big}"}
6 - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line| 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,17 +18,17 @@
18 - else 18 - else
19 %ul.well-list= render Commit.decorate(@commits), project: @project 19 %ul.well-list= render Commit.decorate(@commits), project: @project
20 20
21 - %h4 Diff 21 + %h4 Changes
22 - if @diffs.present? 22 - if @diffs.present?
23 = render "projects/commits/diffs", diffs: @diffs, project: @project 23 = render "projects/commits/diffs", diffs: @diffs, project: @project
24 - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE 24 - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
25 .bs-callout.bs-callout-danger 25 .bs-callout.bs-callout-danger
26 %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. 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 - elsif @timeout 28 - elsif @timeout
29 .bs-callout.bs-callout-danger 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 - else 34 - else
app/views/projects/deploy_keys/show.html.haml
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 Deploy key: 2 Deploy key:
3 = @key.title 3 = @key.title
4 %small 4 %small
5 - created at 5 + created on
6 = @key.created_at.stamp("Aug 21, 2011") 6 = @key.created_at.stamp("Aug 21, 2011")
7 .back-link 7 .back-link
8 = link_to project_deploy_keys_path(@project) do 8 = link_to project_deploy_keys_path(@project) do
app/views/projects/issues/_form.html.haml
1 %div.issue-form-holder 1 %div.issue-form-holder
2 %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" 2 %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}"
3 %hr 3 %hr
4 - - if @repository.contribution_guide && !@issue.persisted? 4 + - if !@repository.empty? && @repository.contribution_guide && !@issue.persisted?
5 - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) 5 - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
6 .alert.alert-info.col-sm-10.col-sm-offset-2 6 .alert.alert-info.col-sm-10.col-sm-offset-2
7 ="Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe 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,7 +20,7 @@
20 %li.diffs-tab{data: {action: 'diffs'}} 20 %li.diffs-tab{data: {action: 'diffs'}}
21 = link_to diffs_project_merge_request_path(@project, @merge_request) do 21 = link_to diffs_project_merge_request_path(@project, @merge_request) do
22 %i.icon-list-alt 22 %i.icon-list-alt
23 - Diff 23 + Changes
24 24
25 - content_for :note_actions do 25 - content_for :note_actions do
26 - if can?(current_user, :modify_merge_request, @merge_request) 26 - if can?(current_user, :modify_merge_request, @merge_request)
app/views/projects/merge_requests/show/_diffs.html.haml
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 - else 5 - else
6 .bs-callout.bs-callout-warning 6 .bs-callout.bs-callout-warning
7 %h4 7 %h4
8 - Diff for this comparison is extremely large. 8 + Changes view for this comparison is extremely large.
9 %p 9 %p
10 You can 10 You can
11 = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request, format: :diff), class: "vlink" 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,7 +4,10 @@
4 %strong Archived projects cannot be committed to! 4 %strong Archived projects cannot be committed to!
5 - else 5 - else
6 .bs-callout 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 - if @show_merge_controls 13 - if @show_merge_controls
app/views/users_groups/_users_group.html.haml
@@ -2,11 +2,12 @@ @@ -2,11 +2,12 @@
2 - return unless user 2 - return unless user
3 - show_roles = true if show_roles.nil? 3 - show_roles = true if show_roles.nil?
4 %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} 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 - if show_roles 12 - if show_roles
12 %span.pull-right 13 %span.pull-right
@@ -22,7 +23,7 @@ @@ -22,7 +23,7 @@
22 - else 23 - else
23 = 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 = 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 %i.icon-minus.icon-white 25 %i.icon-minus.icon-white
25 - 26 +
26 .edit-member.hide.js-toggle-content 27 .edit-member.hide.js-toggle-content
27 = form_for [@group, member], remote: true do |f| 28 = form_for [@group, member], remote: true do |f|
28 .alert.prepend-top-20 29 .alert.prepend-top-20
config/gitlab.yml.example
@@ -76,6 +76,11 @@ production: &amp;base @@ -76,6 +76,11 @@ production: &amp;base
76 snippets: false 76 snippets: false
77 visibility_level: "private" # can be "private" | "internal" | "public" 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 ## External issues trackers 84 ## External issues trackers
80 issues_tracker: 85 issues_tracker:
81 # redmine: 86 # redmine:
config/initializers/1_settings.rb
@@ -97,6 +97,7 @@ Settings.gitlab.default_projects_features[&#39;wiki&#39;] = true if Settings.g @@ -97,6 +97,7 @@ Settings.gitlab.default_projects_features[&#39;wiki&#39;] = true if Settings.g
97 Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil? 97 Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil?
98 Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? 98 Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
99 Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) 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 # Gravatar 103 # Gravatar
doc/README.md 0 → 100644
@@ -0,0 +1,18 @@ @@ -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 # GitLab API 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 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. 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 If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401: 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,6 +130,10 @@ When listing resources you can pass the following parameters:
103 + `page` (default: `1`) - page number 130 + `page` (default: `1`) - page number
104 + `per_page` (default: `20`, max: `100`) - number of items to list per page 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 ## id vs iid 137 ## id vs iid
107 138
108 When you work with API you may notice two similar fields in api entites: id and iid. 139 When you work with API you may notice two similar fields in api entites: id and iid.
@@ -117,30 +148,3 @@ Issue @@ -117,30 +148,3 @@ Issue
117 148
118 So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json` 149 So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`
119 But when you want to create a link to web page - use `http:://host/project/issues/:iid.json` 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 @@ @@ -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 ## List project repository tags 1 ## List project repository tags
169 2
170 Get a list of repository tags from a project, sorted by name in reverse alphabetical order. 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,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 ## Single user 59 ## Single user
56 60
doc/development/README.md 0 → 100644
@@ -0,0 +1,2 @@ @@ -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,7 +28,7 @@ To serve repositories over SSH there&#39;s an add-on application called gitlab-shell
28 28
29 ## Components 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 A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS. 33 A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS.
34 It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. 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,4 +180,4 @@ bundle exec rake gitlab:check RAILS_ENV=production
180 ``` 180 ```
181 181
182 Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`. 182 Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`.
183 -While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL. 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 \ No newline at end of file 185 \ No newline at end of file
doc/install/README.md 0 → 100644
@@ -0,0 +1,4 @@ @@ -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 +6,9 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
6 6
7 # Install the database packages 7 # Install the database packages
8 sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev 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 # Pick a database root password (can be anything), type it and press enter 13 # Pick a database root password (can be anything), type it and press enter
11 # Retype the database root password and press enter 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,6 +26,10 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
23 # change $password in the command below to a real password you pick 26 # change $password in the command below to a real password you pick
24 mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; 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 # Create the GitLab production database 33 # Create the GitLab production database
27 mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; 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 @@ @@ -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,7 +38,7 @@ If a user is a GitLab administrator they receive all permissions.
38 |------|-----|--------|---------|------|-----| 38 |------|-----|--------|---------|------|-----|
39 |Browse group|✓|✓|✓|✓|✓| 39 |Browse group|✓|✓|✓|✓|✓|
40 |Edit group|||||✓| 40 |Edit group|||||✓|
41 -|create project in group|||||✓| 41 +|Create project in group|||||✓|
42 |Manage group members|||||✓| 42 |Manage group members|||||✓|
43 |Remove group|||||✓| 43 |Remove group|||||✓|
44 44
doc/raketasks/README.md 0 → 100644
@@ -0,0 +1,6 @@ @@ -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 @@ @@ -0,0 +1,2 @@
  1 ++ [Monthly](monthly.md)
  2 ++ [Security](security.md)
doc/security/README.md 0 → 100644
@@ -0,0 +1,2 @@ @@ -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 @@ @@ -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,4 +9,4 @@ After this the machine that uses the corresponding private key has read-only acc
9 9
10 You can't add the same deploy key twice with the 'New Deploy Key' option. 10 You can't add the same deploy key twice with the 'New Deploy Key' option.
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'. 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,3 +140,6 @@ Follow the [`upgrade guide from 5.4 to 6.0`](5.4-to-6.0.md), except for the data
140 cd /home/git/gitlab 140 cd /home/git/gitlab
141 sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production 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 @@ @@ -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,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 @@ @@ -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,24 +3,25 @@
3 ```bash 3 ```bash
4 git clone git@example.com:project-name.git 4 git clone git@example.com:project-name.git
5 ``` 5 ```
  6 +
6 2. Create branch with your feature 7 2. Create branch with your feature
7 8
8 ```bash 9 ```bash
9 git checkout -b $feature_name 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 ```bash 15 ```bash
15 git commit -am "My feature is ready" 16 git commit -am "My feature is ready"
16 ``` 17 ```
17 18
18 4. Push your branch to GitLab 19 4. Push your branch to GitLab
19 - 20 +
20 ```bash 21 ```bash
21 git push origin $feature_name 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 6. Create a merge request 26 6. Create a merge request
26 7. Your team lead will review the code &amp; merge it to the main branch 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,5 +45,6 @@ module API
45 mount Files 45 mount Files
46 mount Commits 46 mount Commits
47 mount Namespaces 47 mount Namespaces
  48 + mount Branches
48 end 49 end
49 end 50 end
lib/api/branches.rb 0 → 100644
@@ -0,0 +1,85 @@ @@ -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 require 'mime/types' 1 require 'mime/types'
2 2
3 module API 3 module API
4 - # Projects API 4 + # Projects commits API
5 class Commits < Grape::API 5 class Commits < Grape::API
6 before { authenticate! } 6 before { authenticate! }
7 before { authorize! :download_code, user_project } 7 before { authorize! :download_code, user_project }
8 8
9 resource :projects do 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 # Get a project repository commits 10 # Get a project repository commits
20 # 11 #
21 # Parameters: 12 # Parameters:
lib/api/helpers.rb
@@ -56,8 +56,12 @@ module API @@ -56,8 +56,12 @@ module API
56 end 56 end
57 end 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 end 65 end
62 66
63 def authenticate! 67 def authenticate!
@@ -74,6 +78,10 @@ module API @@ -74,6 +78,10 @@ module API
74 end 78 end
75 end 79 end
76 80
  81 + def authorize_push_project
  82 + authorize! :push_code, user_project
  83 + end
  84 +
77 def authorize_admin_project 85 def authorize_admin_project
78 authorize! :admin_project, user_project 86 authorize! :admin_project, user_project
79 end 87 end
@@ -134,6 +142,18 @@ module API @@ -134,6 +142,18 @@ module API
134 142
135 private 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 def abilities 157 def abilities
138 @abilities ||= begin 158 @abilities ||= begin
139 abilities = Six.new 159 abilities = Six.new
lib/api/repositories.rb
@@ -15,66 +15,6 @@ module API @@ -15,66 +15,6 @@ module API
15 not_found! 15 not_found!
16 end 16 end
17 end 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 # Get a project repository tags 18 # Get a project repository tags
79 # 19 #
80 # Parameters: 20 # Parameters:
@@ -161,7 +101,7 @@ module API @@ -161,7 +101,7 @@ module API
161 repo = user_project.repository 101 repo = user_project.repository
162 ref = params[:sha] 102 ref = params[:sha]
163 format = params[:format] 103 format = params[:format]
164 - storage_path = Rails.root.join("tmp", "repositories") 104 + storage_path = Gitlab.config.gitlab.repository_downloads_path
165 105
166 file_path = repo.archive_repo(ref, storage_path, format) 106 file_path = repo.archive_repo(ref, storage_path, format)
167 if file_path && File.exists?(file_path) 107 if file_path && File.exists?(file_path)
lib/backup/database.rb
@@ -29,9 +29,10 @@ module Backup @@ -29,9 +29,10 @@ module Backup
29 print "Restoring MySQL database #{config['database']} ... " 29 print "Restoring MySQL database #{config['database']} ... "
30 system('mysql', *mysql_args, config['database'], in: db_file_name) 30 system('mysql', *mysql_args, config['database'], in: db_file_name)
31 when "postgresql" then 31 when "postgresql" then
32 - puts "Destructively rebuilding database schema for RAILS_ENV #{Rails.env}"  
33 - Rake::Task["db:schema:load"].invoke  
34 print "Restoring PostgreSQL database #{config['database']} ... " 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 pg_env 36 pg_env
36 system('psql', config['database'], '-f', db_file_name) 37 system('psql', config['database'], '-f', db_file_name)
37 end 38 end
lib/gitlab/ldap/user.rb
@@ -81,16 +81,17 @@ module Gitlab @@ -81,16 +81,17 @@ module Gitlab
81 81
82 private 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 end 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 end 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 end 95 end
95 96
96 def provider 97 def provider
lib/support/init.d/gitlab
@@ -149,7 +149,7 @@ exit_if_not_running(){ @@ -149,7 +149,7 @@ exit_if_not_running(){
149 } 149 }
150 150
151 ## Starts Unicorn and Sidekiq if they're not running. 151 ## Starts Unicorn and Sidekiq if they're not running.
152 -start() { 152 +start_gitlab() {
153 check_stale_pids 153 check_stale_pids
154 154
155 if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then 155 if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then
@@ -167,7 +167,7 @@ start() { @@ -167,7 +167,7 @@ start() {
167 # Remove old socket if it exists 167 # Remove old socket if it exists
168 rm -f "$socket_path"/gitlab.socket 2>/dev/null 168 rm -f "$socket_path"/gitlab.socket 2>/dev/null
169 # Start the web server 169 # Start the web server
170 - RAILS_ENV=$RAILS_ENV script/web start & 170 + RAILS_ENV=$RAILS_ENV script/web start
171 fi 171 fi
172 172
173 # If sidekiq is already running, don't start it again. 173 # If sidekiq is already running, don't start it again.
@@ -184,7 +184,7 @@ start() { @@ -184,7 +184,7 @@ start() {
184 } 184 }
185 185
186 ## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them. 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 exit_if_not_running 188 exit_if_not_running
189 189
190 if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then 190 if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then
@@ -246,7 +246,7 @@ print_status() { @@ -246,7 +246,7 @@ print_status() {
246 } 246 }
247 247
248 ## Tells unicorn to reload it's config and Sidekiq to restart 248 ## Tells unicorn to reload it's config and Sidekiq to restart
249 -reload(){ 249 +reload_gitlab(){
250 exit_if_not_running 250 exit_if_not_running
251 if [ "$wpid" = "0" ];then 251 if [ "$wpid" = "0" ];then
252 echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded." 252 echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded."
@@ -263,12 +263,12 @@ reload(){ @@ -263,12 +263,12 @@ reload(){
263 } 263 }
264 264
265 ## Restarts Sidekiq and Unicorn. 265 ## Restarts Sidekiq and Unicorn.
266 -restart(){ 266 +restart_gitlab(){
267 check_status 267 check_status
268 if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then 268 if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
269 - stop 269 + stop_gitlab
270 fi 270 fi
271 - start 271 + start_gitlab
272 } 272 }
273 273
274 274
@@ -276,16 +276,16 @@ restart(){ @@ -276,16 +276,16 @@ restart(){
276 276
277 case "$1" in 277 case "$1" in
278 start) 278 start)
279 - start 279 + start_gitlab
280 ;; 280 ;;
281 stop) 281 stop)
282 - stop 282 + stop_gitlab
283 ;; 283 ;;
284 restart) 284 restart)
285 - restart 285 + restart_gitlab
286 ;; 286 ;;
287 reload|force-reload) 287 reload|force-reload)
288 - reload 288 + reload_gitlab
289 ;; 289 ;;
290 status) 290 status)
291 print_status 291 print_status
lib/tasks/gitlab/check.rake
@@ -677,7 +677,20 @@ namespace :gitlab do @@ -677,7 +677,20 @@ namespace :gitlab do
677 end 677 end
678 678
679 def filter 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 end 694 end
682 695
683 def ldap 696 def ldap
lib/tasks/gitlab/db/drop_all_tables.rake 0 → 100644
@@ -0,0 +1,10 @@ @@ -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,6 +292,20 @@ describe User do
292 end 292 end
293 end 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 describe 'by_username_or_id' do 309 describe 'by_username_or_id' do
296 let(:user1) { create(:user, username: 'foo') } 310 let(:user1) { create(:user, username: 'foo') }
297 311
spec/requests/api/branches_spec.rb 0 → 100644
@@ -0,0 +1,115 @@ @@ -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,6 +25,12 @@ describe API::API do
25 json_response.should be_an Array 25 json_response.should be_an Array
26 json_response.first['title'].should == issue.title 26 json_response.first['title'].should == issue.title
27 end 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 end 34 end
29 end 35 end
30 36
spec/requests/api/repositories_spec.rb
@@ -14,86 +14,6 @@ describe API::API do @@ -14,86 +14,6 @@ describe API::API do
14 14
15 before { project.team << [user, :reporter] } 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 describe "GET /projects/:id/repository/tags" do 17 describe "GET /projects/:id/repository/tags" do
98 it "should return an array of project tags" do 18 it "should return an array of project tags" do
99 get api("/projects/#{project.id}/repository/tags", user) 19 get api("/projects/#{project.id}/repository/tags", user)