Commit cb659e4c5a1c32bd2aa5c9994cd9d7b4a9841c9b
Exists in
spb-stable
and in
3 other branches
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
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/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) | ... | ... |
... | ... | @@ -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
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: &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['wiki'] = 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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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
doc/development/architecture.md
... | ... | @@ -28,7 +28,7 @@ To serve repositories over SSH there's an add-on application called gitlab-shell |
28 | 28 | |
29 | 29 | ## Components |
30 | 30 | |
31 | - | |
31 | + | |
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/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/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/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/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 | -``` |
... | ... | @@ -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 & merge it to the main branch | ... | ... |
lib/api/api.rb
... | ... | @@ -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 | ... | ... |
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 | ... | ... |
... | ... | @@ -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) | ... | ... |