diff --git a/CHANGELOG b/CHANGELOG index e0218dc939d..d936986a779 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,11 @@ v 6.8.0 - Ability to at mention users that are participating in issue and merge req. discussion - 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) - - Add support for relative submodules + - Make user search case-insensitive (Christopher Arnold) + - Remove omniauth-ldap nickname bug workaround + - Drop all tables before restoring a Postgres backup + - Make the repository downloads path configurable + - Create branches via API (sponsored by O'Reilly Media) v 6.7.2 - Fix upgrader script diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d864df14868..bdf5e09831a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,10 +63,11 @@ If you can, please submit a merge request with the fix or improvements including 1. Add your changes to the [CHANGELOG](CHANGELOG) 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) 1. Push the commit to your fork -1. Submit a merge request (MR) +1. Submit a merge request (MR) to the master branch 1. The MR title should describes the change you want to make 1. The MR description should give a motive for your change and the method you used to achieve it 1. If the MR changes the UI it should include before and after screenshots +1. If the MR changes CSS classes please include the list of affected pages `grep css-class ./app -R` 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 1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion 1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md). diff --git a/Guardfile b/Guardfile index e682f0b6cf1..e19a312377d 100644 --- a/Guardfile +++ b/Guardfile @@ -1,7 +1,7 @@ # A sample Guardfile # More info at https://github.com/guard/guard#readme -guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do +guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_pass: false do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" } diff --git a/README.md b/README.md index 7818d40ac55..fcc40f1b917 100644 --- a/README.md +++ b/README.md @@ -113,38 +113,10 @@ or start each component separately Single Spinach test: bundle exec spinach features/project/issues/milestones.feature -### GitLab interfaces +### Documentation -* [GitLab API doc](doc/api/README.md) or see the [GitLab API website](http://api.gitlab.org/) - -* [Rake tasks](doc/raketasks) including a [backup and restore procedure](doc/raketasks/backup_restore.md) - -* [Directory structure](doc/install/structure.md) - -* [Database installation](doc/install/databases.md) - -* [Markdown specification](doc/markdown/markdown.md) - -* [Security guide](doc/security/rack_attack.md) to throttle abusive requests +All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/). ### Getting help -* [Maintenance policy](MAINTENANCE.md) specifies what versions are supported. - -* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) contains solutions to common problems. - -* [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. - -* [Feature request forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab. - -* [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. - -* [Support subscription](http://www.gitlab.com/subscription/) connects you to the knowledge of GitLab experts that will resolve your issues and answer your questions. - -* [Consultancy](http://www.gitlab.com/consultancy/) from the GitLab experts for installations, upgrades and customizations. - -* [#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). - -* [Book](http://www.packtpub.com/gitlab-repository-management/book) written by GitLab enthusiast Jonathan M. Hethey is unofficial but it offers a good overview. - -* [Gitter chat room](https://gitter.im/gitlabhq/gitlabhq#) here you can ask questions when you need help. +Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help. diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss index de70e47333f..963768044d5 100644 --- a/app/assets/stylesheets/generic/lists.scss +++ b/app/assets/stylesheets/generic/lists.scss @@ -13,6 +13,12 @@ border-bottom: 1px solid #eee; border-bottom: 1px solid rgba(0, 0, 0, 0.05); + &:after { + content: " "; + display: table; + clear: both; + } + &.disabled { color: #888; } @@ -46,6 +52,12 @@ .author { color: #999; } + .list-item-name { + float: left; + position: relative; + top: 3px; + } + p { padding-top: 1px; margin: 0; diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index e088ef82203..e70757e0406 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -113,6 +113,5 @@ float: left; margin-right: 3px; color: #999; - margin-bottom: 10px; width: 16px; } diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss index 813566c4def..eb272f20f40 100644 --- a/app/assets/stylesheets/sections/diff.scss +++ b/app/assets/stylesheets/sections/diff.scss @@ -62,6 +62,29 @@ font-size: 12px; } } + + .text-file-parallel div { + display: inline-block; + padding-bottom: 16px; + } + .diff-side { + overflow-x: scroll; + width: 508px; + height: 700px; + } + .diff-side.diff-side-left{ + overflow-y:hidden; + } + .diff-side table, td.diff-middle table { + height: 700px; + } + .diff-middle { + width: 114px; + vertical-align: top; + height: 700px; + overflow: hidden + } + .old_line, .new_line, .diff_line { margin: 0px; padding: 0px; @@ -125,8 +148,6 @@ } &.parallel { display: table-cell; - overflow: hidden; - width: 50%; } } } diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index aa6914414ce..6a6cbe48184 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -16,11 +16,7 @@ class Projects::BranchesController < Projects::ApplicationController end def create - @repository.add_branch(params[:branch_name], params[:ref]) - - if new_branch = @repository.find_branch(params[:branch_name]) - Event.create_ref_event(@project, current_user, new_branch, 'add') - end + CreateBranchService.new.execute(project, params[:branch_name], params[:ref], current_user) redirect_to project_branches_path(@project) end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index f686db70bd4..d1a6fecba20 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -14,7 +14,7 @@ class Projects::RepositoriesController < Projects::ApplicationController render_404 and return end - storage_path = Rails.root.join("tmp", "repositories") + storage_path = Gitlab.config.gitlab.repository_downloads_path file_path = @repository.archive_repo(params[:ref], storage_path, params[:format].downcase) diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 5e5f3f77a21..c6e4f574b67 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -105,8 +105,80 @@ module CommitsHelper branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe end - def get_old_file(project, commit, diff) - project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id + def parallel_diff_lines(project, commit, diff, file) + old_file = project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id + deleted_lines = {} + added_lines = {} + each_diff_line(diff, 0) do |line, type, line_code, line_new, line_old| + if type == "old" + deleted_lines[line_old] = { line_code: line_code, type: type, line: line } + elsif type == "new" + added_lines[line_new] = { line_code: line_code, type: type, line: line } + end + end + max_length = old_file ? old_file.sloc + added_lines.length : file.sloc + + offset1 = 0 + offset2 = 0 + old_lines = [] + new_lines = [] + + max_length.times do |line_index| + line_index1 = line_index - offset1 + line_index2 = line_index - offset2 + deleted_line = deleted_lines[line_index1 + 1] + added_line = added_lines[line_index2 + 1] + old_line = old_file.lines[line_index1] if old_file + new_line = file.lines[line_index2] + + if deleted_line && added_line + elsif deleted_line + new_line = nil + offset2 += 1 + elsif added_line + old_line = nil + offset1 += 1 + end + + old_lines[line_index] = DiffLine.new + new_lines[line_index] = DiffLine.new + + # old + if line_index == 0 && diff.new_file + old_lines[line_index].type = :file_created + old_lines[line_index].content = 'File was created' + elsif deleted_line + old_lines[line_index].type = :deleted + old_lines[line_index].content = old_line + old_lines[line_index].num = line_index1 + 1 + old_lines[line_index].code = deleted_line[:line_code] + elsif old_line + old_lines[line_index].type = :no_change + old_lines[line_index].content = old_line + old_lines[line_index].num = line_index1 + 1 + else + old_lines[line_index].type = :added + end + + # new + if line_index == 0 && diff.deleted_file + new_lines[line_index].type = :file_deleted + new_lines[line_index].content = "File was deleted" + elsif added_line + new_lines[line_index].type = :added + new_lines[line_index].num = line_index2 + 1 + new_lines[line_index].content = new_line + new_lines[line_index].code = added_line[:line_code] + elsif new_line + new_lines[line_index].type = :no_change + new_lines[line_index].num = line_index2 + 1 + new_lines[line_index].content = new_line + else + new_lines[line_index].type = :deleted + end + end + + return old_lines, new_lines end protected diff --git a/app/models/diff_line.rb b/app/models/diff_line.rb new file mode 100644 index 00000000000..ad37945874a --- /dev/null +++ b/app/models/diff_line.rb @@ -0,0 +1,3 @@ +class DiffLine + attr_accessor :type, :content, :num, :code +end diff --git a/app/models/user.rb b/app/models/user.rb index d9f420759d2..25c10a6faa0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -204,7 +204,7 @@ class User < ActiveRecord::Base end def search query - where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%") + where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%") end def by_username_or_id(name_or_id) diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb new file mode 100644 index 00000000000..98beeee8354 --- /dev/null +++ b/app/services/create_branch_service.rb @@ -0,0 +1,13 @@ +class CreateBranchService + def execute(project, branch_name, ref, current_user) + repository = project.repository + repository.add_branch(branch_name, ref) + new_branch = repository.find_branch(branch_name) + + if new_branch + Event.create_ref_event(project, current_user, new_branch, 'add') + end + + new_branch + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 6fda9868aa5..d85398809c2 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -178,21 +178,41 @@ class NotificationService # Get project users with WATCH notification level def project_watchers(project) - project_watchers = [] - member_methods = { project => :users_projects } - member_methods.merge!(project.group => :users_groups) if project.group + # Gather all user ids that have WATCH notification setting for project + project_notification_uids = project_notification_list(project, Notification::N_WATCH) - member_methods.each do |object, member_method| - # Get project notification settings since it has higher priority - user_ids = object.send(member_method).where(notification_level: Notification::N_WATCH).pluck(:user_id) - project_watchers += User.where(id: user_ids) + # Gather all user ids that have WATCH notification setting for group + group_notification_uids = group_notification_list(project, Notification::N_WATCH) - # next collect users who use global settings with watch state - user_ids = object.send(member_method).where(notification_level: Notification::N_GLOBAL).pluck(:user_id) - project_watchers += User.where(id: user_ids, notification_level: Notification::N_WATCH) + # Gather all user ids that have GLOBAL setting + global_notification_uids = global_notification_list(project) + + project_and_group_uids = [project_notification_uids, group_notification_uids].flatten.uniq + group_and_project_watchers = User.where(id: project_and_group_uids) + + # Find all users that have WATCH as their GLOBAL setting + global_watchers = User.where(id: global_notification_uids, notification_level: Notification::N_WATCH) + + [group_and_project_watchers, global_watchers].flatten.uniq + end + + def project_notification_list(project, notification_level) + project.users_projects.where(notification_level: notification_level).pluck(:user_id) + end + + def group_notification_list(project, notification_level) + if project.group + project.group.users_groups.where(notification_level: notification_level).pluck(:user_id) + else + [] end + end - project_watchers.uniq + def global_notification_list(project) + [ + project_notification_list(project, Notification::N_GLOBAL), + group_notification_list(project, Notification::N_GLOBAL) + ].flatten end # Remove users with disabled notifications from array diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 252111875fa..6055865b4cb 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -70,8 +70,9 @@ - @group.users_groups.order('group_access DESC').each do |member| - user = member.user %li{class: dom_class(user)} - %strong - = link_to user.name, admin_user_path(user) + .list-item-name + %strong + = link_to user.name, admin_user_path(user) %span.pull-right.light = member.human_access = 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 diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index ff90d513ca1..60773c20097 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -28,8 +28,10 @@ %ul.well-list - @hooks.each do |hook| %li + .list-item-name + = link_to admin_hook_path(hook) do + %strong= hook.url + .pull-right = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small" = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" - = link_to admin_hook_path(hook) do - %strong= hook.url diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 5f19d21f106..296094ab29c 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -44,9 +44,10 @@ %ul.well-list - @projects.each do |project| %li - %span{ class: visibility_level_color(project.visibility_level) } - = visibility_level_icon(project.visibility_level) - = link_to project.name_with_namespace, [:admin, project] + .list-item-name + %span{ class: visibility_level_color(project.visibility_level) } + = visibility_level_icon(project.visibility_level) + = link_to project.name_with_namespace, [:admin, project] .pull-right %span.label.label-gray = repository_size(project) diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 34a91cce163..11f07743ace 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -116,8 +116,9 @@ - @project.users_projects.each do |users_project| - user = users_project.user %li.users_project - %strong - = link_to user.name, admin_user_path(user) + .list-item-name + %strong + = link_to user.name, admin_user_path(user) .pull-right - if users_project.owner? %span.light Owner diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 0b3934a712d..f42ae7c6a01 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -36,15 +36,16 @@ %ul.well-list - @users.each do |user| %li - - if user.blocked? - %i.icon-lock.cred - - else - %i.icon-user.cgreen - = link_to user.name, [:admin, user] - - if user.admin? - %strong.cred (Admin) - - if user == current_user - %span.cred It's you! + .list-item-name + - if user.blocked? + %i.icon-lock.cred + - else + %i.icon-user.cgreen + = link_to user.name, [:admin, user] + - if user.admin? + %strong.cred (Admin) + - if user == current_user + %span.cred It's you! .pull-right %span.light %i.icon-envelope diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index d66119e2712..764b34499ab 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -124,7 +124,8 @@ - @user.users_groups.each do |user_group| - group = user_group.group %li.users_group - %strong= link_to group.name, admin_group_path(group) + %span{class: ("list-item-name" unless user_group.owner?)} + %strong= link_to group.name, admin_group_path(group) .pull-right %span.light= user_group.human_access - unless user_group.owner? diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 9308bd8124e..500c37ab71d 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -73,8 +73,9 @@ %ul.well-list - @group.projects.each do |project| %li - = visibility_level_icon(project.visibility_level) - = link_to project.name_with_namespace, project + .list-item-name + = visibility_level_icon(project.visibility_level) + = link_to project.name_with_namespace, project .pull-right = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 09d84a3eb9f..3e8dae0b230 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -23,4 +23,4 @@ - if @project You're receiving this notification because you are a member of the #{link_to @project.name_with_namespace, project_url(@project)} project team. - if @target_url - #{link_to "View in GitLab", @target_url} + #{link_to "View it on GitLab", @target_url} diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index ab0d6c653b9..85a01a567f3 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -7,7 +7,7 @@ %li #{commit.short_id} - #{commit.title} -%h4 Diff: +%h4 Changes: - @diffs.each do |diff| %li %strong @@ -23,6 +23,6 @@ %br - if @compare.timeout - %h5 Huge diff. To prevent performance issues it was hidden + %h5 To prevent performance issues changes are hidden - elsif @compare.commits_over_limit? - %h5 Diff for big amount of commits is disabled + %h5 Changes are not shown due to large amount of commits diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml index 93b344d2c82..b8d7fbeb046 100644 --- a/app/views/notify/repository_push_email.text.haml +++ b/app/views/notify/repository_push_email.text.haml @@ -6,7 +6,7 @@ Commits: #{commit.short_id} - #{truncate(commit.title, length: 40)} \ \ -Diff: +Changes: - @diffs.each do |diff| \ \===================================== @@ -22,4 +22,4 @@ Diff: - if @compare.timeout Huge diff. To prevent performance issues it was hidden - elsif @compare.commits_over_limit? - Diff for big amount of commits is disabled + Changes are not shown due to large amount of commits diff --git a/app/views/projects/commits/_parallel_view.html.haml b/app/views/projects/commits/_parallel_view.html.haml index 3234e9da0ac..5b60ab80ba4 100644 --- a/app/views/projects/commits/_parallel_view.html.haml +++ b/app/views/projects/commits/_parallel_view.html.haml @@ -1,75 +1,55 @@ / Side-by-side diff view -- old_file = get_old_file(project, @commit, diff) -- deleted_lines = {} -- added_lines = {} -- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line| - - if type == "old" - - deleted_lines[line_old] = { line_code: line_code, type: type, line: line } - - elsif type == "new" - - added_lines[line_new] = { line_code: line_code, type: type, line: line } - -- max_length = old_file.sloc + added_lines.length if old_file -- max_length ||= file.sloc -- offset1 = 0 -- offset2 = 0 +- old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file) +- num_lines = old_lines.length %div.text-file-parallel - %table{ style: "table-layout: fixed;" } - - max_length.times do |line_index| - - line_index1 = line_index - offset1 - - line_index2 = line_index - offset2 - - deleted_line = deleted_lines[line_index1 + 1] - - added_line = added_lines[line_index2 + 1] - - old_line = old_file.lines[line_index1] if old_file - - new_line = file.lines[line_index2] + %div.diff-side.diff-side-left + %table + - old_lines.each do |line| - - if deleted_line && added_line - - elsif deleted_line - - new_line = nil - - offset2 += 1 - - elsif added_line - - old_line = nil - - offset1 += 1 + %tr.line_holder.parallel + - if line.type == :file_created + %td.line_content.parallel= "File was created" + - elsif line.type == :deleted + %td.line_content{class: "parallel noteable_line old #{line.code}", "line_code" => line.code }= line.content + - else line.type == :no_change + %td.line_content.parallel= line.content - %tr.line_holder.parallel - - if line_index == 0 && diff.new_file - %td.line_content.parallel= "File was created" - %td.old_line= "" - - elsif deleted_line - %td.line_content{class: "parallel noteable_line old #{deleted_line[:line_code]}", "line_code" => deleted_line[:line_code] }= old_line - %td.old_line.old - = line_index1 + 1 - - if @comments_allowed - =# render "projects/notes/diff_note_link", line_code: deleted_line[:line_code] - - elsif old_line - %td.line_content.parallel= old_line - %td.old_line= line_index1 + 1 - - else - %td.line_content.parallel= "" - %td.old_line= "" + %div.diff-middle + %table + - num_lines.times do |index| + %tr + - if old_lines[index].type == :deleted + %td.old_line.old= old_lines[index].num + - else + %td.old_line= old_lines[index].num - %td.diff_line= "" + %td.diff_line="" - - if diff.deleted_file && line_index == 0 - %td.new_line= "" - %td.line_content.parallel= "File was deleted" - - elsif added_line - %td.new_line.new - = line_index2 + 1 - - if @comments_allowed - =# render "projects/notes/diff_note_link", line_code: added_line[:line_code] - %td.line_content{class: "parallel noteable_line new #{added_line[:line_code]}", "line_code" => added_line[:line_code] }= new_line - - elsif new_line - %td.new_line= line_index2 + 1 - %td.line_content.parallel= new_line - - else - %td.new_line= "" - %td.line_content.parallel= "" + - if new_lines[index].type == :added + %td.new_line.new= new_lines[index].num + - else + %td.new_line= new_lines[index].num - - if @reply_allowed - - comments1 = [] - - comments2 = [] - - comments1 = @line_notes.select { |n| n.line_code == deleted_line[:line_code] }.sort_by(&:created_at) if deleted_line - - comments2 = @line_notes.select { |n| n.line_code == added_line[:line_code] }.sort_by(&:created_at) if added_line - - unless comments1.empty? && comments2.empty? - = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2, line1: deleted_line, line2: added_line \ No newline at end of file + %div.diff-side.diff-side-right + %table + - new_lines.each do |line| + + %tr.line_holder.parallel + - if line.type == :file_deleted + %td.line_content.parallel= "File was deleted" + - elsif line.type == :added + %td.line_content{class: "parallel noteable_line new #{line.code}", "line_code" => line.code }= line.content + - else line.type == :no_change + %td.line_content.parallel= line.content + +:javascript + $('.diff-side-right').on('scroll', function(){ + $('.diff-side-left, .diff-middle').scrollTop($(this).scrollTop()); + $('.diff-side-left').scrollLeft($(this).scrollLeft()); + }); + + $('.diff-side-left').on('scroll', function(){ + $('.diff-side-right, .diff-middle').scrollTop($(this).scrollTop()); // might never be relevant + $('.diff-side-right').scrollLeft($(this).scrollLeft()); + }); diff --git a/app/views/projects/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml index c827d96d855..ba83d2e5a0f 100644 --- a/app/views/projects/commits/_text_file.html.haml +++ b/app/views/projects/commits/_text_file.html.haml @@ -1,6 +1,6 @@ - too_big = diff.diff.lines.count > 1000 - if too_big - %a.supp_diff_link Diff suppressed. Click to show + %a.supp_diff_link Changes suppressed. Click to show %table.text-file{class: "#{'hide' if too_big}"} - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line| diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 9bd49855369..57331bff31b 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -18,17 +18,17 @@ - else %ul.well-list= render Commit.decorate(@commits), project: @project - %h4 Diff + %h4 Changes - if @diffs.present? = render "projects/commits/diffs", diffs: @diffs, project: @project - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE .bs-callout.bs-callout-danger %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. - %p To preserve performance the line diff is not shown. + %p To preserve performance the line changes are not shown. - elsif @timeout .bs-callout.bs-callout-danger - %h4 Diff for this comparison is extremely large. - %p Use command line to browse diff for this comparison. + %h4 Number of changed files for this comparison is extremely large. + %p Use command line to browse through changes for this comparison. - else diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml index 67615e1781f..c66e6bc69c3 100644 --- a/app/views/projects/deploy_keys/show.html.haml +++ b/app/views/projects/deploy_keys/show.html.haml @@ -2,7 +2,7 @@ Deploy key: = @key.title %small - created at + created on = @key.created_at.stamp("Aug 21, 2011") .back-link = link_to project_deploy_keys_path(@project) do diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index dd091302c8e..05cae80e50c 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,7 +1,7 @@ %div.issue-form-holder %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" %hr - - if @repository.contribution_guide && !@issue.persisted? + - if !@repository.empty? && @repository.contribution_guide && !@issue.persisted? - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) .alert.alert-info.col-sm-10.col-sm-offset-2 ="Please review the #{link_to "guidelines for contribution", contribution_guide_url} to this repository.".html_safe diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 809b01918cf..7a21c0dd069 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -20,7 +20,7 @@ %li.diffs-tab{data: {action: 'diffs'}} = link_to diffs_project_merge_request_path(@project, @merge_request) do %i.icon-list-alt - Diff + Changes - content_for :note_actions do - if can?(current_user, :modify_merge_request, @merge_request) diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 7c4f43d2d80..3d48514f98b 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -5,7 +5,7 @@ - else .bs-callout.bs-callout-warning %h4 - Diff for this comparison is extremely large. + Changes view for this comparison is extremely large. %p You can = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request, format: :diff), class: "vlink" diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index 4b1857ccb68..bd7c8435f4c 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -4,7 +4,10 @@ %strong Archived projects cannot be committed to! - else .bs-callout - %strong You don't have permission to merge this MR + .automerge_widget.cannot_be_merged.hide + %strong This can't be merged automatically, even if it could be merged you don't have the permission to do so. + .automerge_widget.can_be_merged.hide + %strong This can be merged automatically but you don't have the permission to do so. - if @show_merge_controls diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml index 1784dab35b4..ad363eaba23 100644 --- a/app/views/users_groups/_users_group.html.haml +++ b/app/views/users_groups/_users_group.html.haml @@ -2,11 +2,12 @@ - return unless user - show_roles = true if show_roles.nil? %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} - = image_tag avatar_icon(user.email, 16), class: "avatar s16" - %strong= user.name - %span.cgray= user.username - - if user == current_user - %span.label.label-success It's you + %span{class: ("list-item-name" if show_controls)} + = image_tag avatar_icon(user.email, 16), class: "avatar s16" + %strong= user.name + %span.cgray= user.username + - if user == current_user + %span.label.label-success It's you - if show_roles %span.pull-right @@ -22,7 +23,7 @@ - else = 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 %i.icon-minus.icon-white - + .edit-member.hide.js-toggle-content = form_for [@group, member], remote: true do |f| .alert.prepend-top-20 diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 9364181eaa4..d9ac1c02ef5 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -76,6 +76,11 @@ production: &base snippets: false visibility_level: "private" # can be "private" | "internal" | "public" + ## Repository downloads directory + # When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory. + # The default is 'tmp/repositories' relative to the root of the Rails app. + # repository_downloads_path: tmp/repositories + ## External issues trackers issues_tracker: # redmine: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index cf6c79bb50e..70ba26cea7a 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -97,6 +97,7 @@ Settings.gitlab.default_projects_features['wiki'] = true if Settings.g Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil? Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) +Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root) # # Gravatar diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 00000000000..d85a2d854d3 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,18 @@ +## The GitLab Documentation covers the following subjects + ++ [API](api/README.md) ++ [Development](development/README.md) ++ [Install](install/README.md) ++ [Integration](integration/external-issue-tracker.md) ++ [Legal](legal/README.md) ++ [Markdown](markdown/markdown.md) ++ [Permissions](permissions/permissions.md) ++ [Public access](public_access/public_access.md) ++ [Raketasks](raketasks/README.md) ++ [Release](release/README.md) ++ [Security](security/README.md) ++ [SSH](ssh/README.md) ++ [System hooks](system_hooks/system_hooks.md) ++ [Update](update/README.md) ++ [Web hooks](web_hooks/web_hooks.md) ++ [Workflow](workflow/workflow.md) diff --git a/doc/api/README.md b/doc/api/README.md index 850666953a3..a2925674f85 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -1,5 +1,32 @@ # GitLab API +## End-points + ++ [Users](users.md) ++ [Session](session.md) ++ [Projects](projects.md) ++ [Project Snippets](project_snippets.md) ++ [Repositories](repositories.md) ++ [Repository Files](repository_files.md) ++ [Commits](commits.md) ++ [Branches](branches.md) ++ [Merge Requests](merge_requests.md) ++ [Issues](issues.md) ++ [Milestones](milestones.md) ++ [Notes](notes.md) ++ [Deploy Keys](deploy_keys.md) ++ [System Hooks](system_hooks.md) ++ [Groups](groups.md) + +## Clients + ++ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP ++ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby ++ [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python ++ [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java + +## Introduction + 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. 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: + `page` (default: `1`) - page number + `per_page` (default: `20`, max: `100`) - number of items to list per page +[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. +These have `rel` prev/next/first/last and contain the relevant url. +Please use these instead of generating your own urls. + ## id vs iid When you work with API you may notice two similar fields in api entites: id and iid. @@ -117,30 +148,3 @@ Issue So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json` But when you want to create a link to web page - use `http:://host/project/issues/:iid.json` - - - -## Contents - -+ [Users](users.md) -+ [Session](session.md) -+ [Projects](projects.md) -+ [Project Snippets](project_snippets.md) -+ [Repositories](repositories.md) -+ [Repository Files](repository_files.md) -+ [Commits](commits.md) -+ [Merge Requests](merge_requests.md) -+ [Issues](issues.md) -+ [Milestones](milestones.md) -+ [Notes](notes.md) -+ [Deploy Keys](deploy_keys.md) -+ [System Hooks](system_hooks.md) -+ [Groups](groups.md) - - -## Clients - -+ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP -+ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby -+ [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python -+ [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java diff --git a/doc/api/branches.md b/doc/api/branches.md new file mode 100644 index 00000000000..3417a695077 --- /dev/null +++ b/doc/api/branches.md @@ -0,0 +1,198 @@ +# Branches + +## List repository branches + +Get a list of repository branches from a project, sorted by name alphabetically. + +``` +GET /projects/:id/repository/branches +``` + +Parameters: + ++ `id` (required) - The ID of a project + +```json +[ + { + "name": "master", + "commit": { + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "parents": [ + { + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + } + ], + "tree": "46e82de44b1061621357f24c05515327f2795a95", + "message": "add projects API", + "author": { + "name": "John Smith", + "email": "john@example.com" + }, + "committer": { + "name": "John Smith", + "email": "john@example.com" + }, + "authored_date": "2012-06-27T05:51:39-07:00", + "committed_date": "2012-06-28T03:44:20-07:00" + }, + "protected": true + } +] +``` + + +## Get single repository branch + +Get a single project repository branch. + +``` +GET /projects/:id/repository/branches/:branch +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `branch` (required) - The name of the branch + +```json +{ + "name": "master", + "commit": { + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "parents": [ + { + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + } + ], + "tree": "46e82de44b1061621357f24c05515327f2795a95", + "message": "add projects API", + "author": { + "name": "John Smith", + "email": "john@example.com" + }, + "committer": { + "name": "John Smith", + "email": "john@example.com" + }, + "authored_date": "2012-06-27T05:51:39-07:00", + "committed_date": "2012-06-28T03:44:20-07:00" + }, + "protected": true +} +``` + + +## Protect repository branch + +Protects a single project repository branch. This is an idempotent function, protecting an already +protected repository branch still returns a `200 Ok` status code. + +``` +PUT /projects/:id/repository/branches/:branch/protect +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `branch` (required) - The name of the branch + +```json +{ + "name": "master", + "commit": { + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "parents": [ + { + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + } + ], + "tree": "46e82de44b1061621357f24c05515327f2795a95", + "message": "add projects API", + "author": { + "name": "John Smith", + "email": "john@example.com" + }, + "committer": { + "name": "John Smith", + "email": "john@example.com" + }, + "authored_date": "2012-06-27T05:51:39-07:00", + "committed_date": "2012-06-28T03:44:20-07:00" + }, + "protected": true +} +``` + + +## Unprotect repository branch + +Unprotects a single project repository branch. This is an idempotent function, unprotecting an already +unprotected repository branch still returns a `200 Ok` status code. + +``` +PUT /projects/:id/repository/branches/:branch/unprotect +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `branch` (required) - The name of the branch + +```json +{ + "name": "master", + "commit": { + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "parents": [ + { + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + } + ], + "tree": "46e82de44b1061621357f24c05515327f2795a95", + "message": "add projects API", + "author": { + "name": "John Smith", + "email": "john@example.com" + }, + "committer": { + "name": "John Smith", + "email": "john@example.com" + }, + "authored_date": "2012-06-27T05:51:39-07:00", + "committed_date": "2012-06-28T03:44:20-07:00" + }, + "protected": false +} +``` + +## Create repository branch + + +``` +POST /projects/:id/repository/branches +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `branch_name` (required) - The name of the branch ++ `ref` (required) - Create branch from commit sha or existing branch + +```json +{ + "name": "my-new-branch", + "commit": { + "id": "8848c0e90327a0b70f1865b843fb2fbfb9345e57", + "message": "Merge pull request #54 from brightbox/use_fog_brightbox_module\n\nUpdate to use fog-brightbox module", + "parent_ids": ["fff449e0bf453576f16c91d6544f00a2664009d8", "f93a93626fec20fd659f4ed3ab2e64019b6169ae"], + "authored_date": "2014-02-20T19:54:55+02:00", + "author_name": "john smith", + "author_email": "john@example.com", + "committed_date": "2014-02-20T19:54:55+02:00", + "committer_name": "john smith", + "committer_email": "john@example.com" + }, + "protected": false +} +``` diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 65ea3615354..3b042e14e72 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -1,170 +1,3 @@ -## List repository branches - -Get a list of repository branches from a project, sorted by name alphabetically. - -``` -GET /projects/:id/repository/branches -``` - -Parameters: - -+ `id` (required) - The ID of a project - -```json -[ - { - "name": "master", - "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, - "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" - }, - "protected": true - } -] -``` - - -## Get single repository branch - -Get a single project repository branch. - -``` -GET /projects/:id/repository/branches/:branch -``` - -Parameters: - -+ `id` (required) - The ID of a project -+ `branch` (required) - The name of the branch - -```json -{ - "name": "master", - "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, - "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" - }, - "protected": true -} -``` - - -## Protect repository branch - -Protects a single project repository branch. This is an idempotent function, protecting an already -protected repository branch still returns a `200 Ok` status code. - -``` -PUT /projects/:id/repository/branches/:branch/protect -``` - -Parameters: - -+ `id` (required) - The ID of a project -+ `branch` (required) - The name of the branch - -```json -{ - "name": "master", - "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, - "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" - }, - "protected": true -} -``` - - -## Unprotect repository branch - -Unprotects a single project repository branch. This is an idempotent function, unprotecting an already -unprotected repository branch still returns a `200 Ok` status code. - -``` -PUT /projects/:id/repository/branches/:branch/unprotect -``` - -Parameters: - -+ `id` (required) - The ID of a project -+ `branch` (required) - The name of the branch - -```json -{ - "name": "master", - "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, - "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" - }, - "protected": false -} -``` - - ## List project repository tags Get a list of repository tags from a project, sorted by name in reverse alphabetical order. diff --git a/doc/api/users.md b/doc/api/users.md index 4098da72b30..1b9eecf2159 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -51,6 +51,10 @@ GET /users ] ``` +You can search for a users by email or username with: +`/users?search=John` + +Also see `def search query` in `app/models/user.rb`. ## Single user diff --git a/doc/development/README.md b/doc/development/README.md new file mode 100644 index 00000000000..aa59eb2c3e1 --- /dev/null +++ b/doc/development/README.md @@ -0,0 +1,2 @@ ++ [Architecture](architecture.md) ++ [Shell commands](shell_commands.md) diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 805e115047a..6f832614d70 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -28,7 +28,7 @@ To serve repositories over SSH there's an add-on application called gitlab-shell ## Components -![GitLab Diagram Overview](resources/gitlab_diagram_overview.png "GitLab Diagram Overview") +![GitLab Diagram Overview](resources/gitlab_diagram_overview.png) A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS. 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 ``` Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`. -While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL. +While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL. \ No newline at end of file diff --git a/doc/install/README.md b/doc/install/README.md new file mode 100644 index 00000000000..ec80e3cd62a --- /dev/null +++ b/doc/install/README.md @@ -0,0 +1,4 @@ ++ [Installation](installation.md) ++ [Requirements](requirements.md) ++ [Structure](structure.md) ++ [Database MySQL](database_mysql.md) diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index 4cf9b94c1a0..bf8183729e7 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -6,6 +6,9 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se # Install the database packages sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev + + # Ensure you have MySQL version 5.5.14 or later + mysql --version # Pick a database root password (can be anything), type it and press enter # 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 # change $password in the command below to a real password you pick mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; + # Ensure you can use the InnoDB engine which is necessary to support long indexes. + # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off" + mysql> SET storage_engine=INNODB; + # Create the GitLab production database mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; diff --git a/doc/legal/README.md b/doc/legal/README.md new file mode 100644 index 00000000000..ebfdad13540 --- /dev/null +++ b/doc/legal/README.md @@ -0,0 +1,2 @@ ++ [Corporate contributor license agreement](corporate_contributor_license_agreement.md) ++ [Individual contributor license agreement](individual_contributor_license_agreement.md) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 73e1728a559..ac4bdefddd5 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -38,7 +38,7 @@ If a user is a GitLab administrator they receive all permissions. |------|-----|--------|---------|------|-----| |Browse group|✓|✓|✓|✓|✓| |Edit group|||||✓| -|create project in group|||||✓| +|Create project in group|||||✓| |Manage group members|||||✓| |Remove group|||||✓| diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md new file mode 100644 index 00000000000..9aa80af12cc --- /dev/null +++ b/doc/raketasks/README.md @@ -0,0 +1,6 @@ ++ [Backup restore](backup_restore.md) ++ [Cleanup](cleanup.md) ++ [Features](features.md) ++ [Maintenance](maintenance.md) ++ [User management](user_management.md) ++ [Web hooks](web_hooks.md) diff --git a/doc/release/README.md b/doc/release/README.md new file mode 100644 index 00000000000..22510be3f18 --- /dev/null +++ b/doc/release/README.md @@ -0,0 +1,2 @@ ++ [Monthly](monthly.md) ++ [Security](security.md) diff --git a/doc/security/README.md b/doc/security/README.md new file mode 100644 index 00000000000..f8dd1291b9b --- /dev/null +++ b/doc/security/README.md @@ -0,0 +1,2 @@ ++ [Password length limits](password_length_limits.md) ++ [Rack attack](rack_attack.md) diff --git a/doc/ssh/README.md b/doc/ssh/README.md new file mode 100644 index 00000000000..76d9e8bb075 --- /dev/null +++ b/doc/ssh/README.md @@ -0,0 +1,2 @@ ++ [Deploy keys](deploy_keys.md) ++ [SSH](ssh.md) diff --git a/doc/ssh/deploy_keys.md b/doc/ssh/deploy_keys.md index 56fe98bb101..c7125b7949e 100644 --- a/doc/ssh/deploy_keys.md +++ b/doc/ssh/deploy_keys.md @@ -9,4 +9,4 @@ After this the machine that uses the corresponding private key has read-only acc You can't add the same deploy key twice with the 'New Deploy Key' option. 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'. -You need to be the owner of the deploy key to see it in this list. +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. diff --git a/doc/update/6.0-to-6.7.md b/doc/update/6.0-to-6.7.md index 68878bb9cd9..5023e34f189 100644 --- a/doc/update/6.0-to-6.7.md +++ b/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 cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` + +## Login issues after upgrade? +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) diff --git a/doc/update/README.md b/doc/update/README.md new file mode 100644 index 00000000000..06e3764616f --- /dev/null +++ b/doc/update/README.md @@ -0,0 +1,5 @@ ++ [The indivual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) ++ [Uprader](upgrader.md) ++ [Ruby](ruby.md) ++ [Patch versions](patch_versions.md) ++ [MySQL to PostgreSQL](mysql_to_postgresql.md) diff --git a/doc/update/mysql-to-postgresql.md b/doc/update/mysql_to_postgresql.md similarity index 100% rename from doc/update/mysql-to-postgresql.md rename to doc/update/mysql_to_postgresql.md diff --git a/doc/workflow/workflow.md b/doc/workflow/workflow.md index 1c238f74957..bb232e9d5c5 100644 --- a/doc/workflow/workflow.md +++ b/doc/workflow/workflow.md @@ -3,24 +3,25 @@ ```bash git clone git@example.com:project-name.git ``` + 2. Create branch with your feature ```bash git checkout -b $feature_name ``` -3. Write code. Comit changes +3. Write code. Commit changes ```bash git commit -am "My feature is ready" ``` 4. Push your branch to GitLab - + ```bash git push origin $feature_name ``` -5. Review your code on Commits page +5. Review your code on commits page 6. Create a merge request 7. Your team lead will review the code & merge it to the main branch diff --git a/lib/api/api.rb b/lib/api/api.rb index 7c4cdad7f0d..ce4cc8b34f7 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -45,5 +45,6 @@ module API mount Files mount Commits mount Namespaces + mount Branches end end diff --git a/lib/api/branches.rb b/lib/api/branches.rb new file mode 100644 index 00000000000..953c6100f8b --- /dev/null +++ b/lib/api/branches.rb @@ -0,0 +1,85 @@ +require 'mime/types' + +module API + # Projects API + class Branches < Grape::API + before { authenticate! } + before { authorize! :download_code, user_project } + + resource :projects do + # Get a project repository branches + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/repository/branches + get ":id/repository/branches" do + present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project + end + + # Get a single branch + # + # Parameters: + # id (required) - The ID of a project + # branch (required) - The name of the branch + # Example Request: + # GET /projects/:id/repository/branches/:branch + get ":id/repository/branches/:branch" do + @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } + not_found!("Branch does not exist") if @branch.nil? + present @branch, with: Entities::RepoObject, project: user_project + end + + # Protect a single branch + # + # Parameters: + # id (required) - The ID of a project + # branch (required) - The name of the branch + # Example Request: + # PUT /projects/:id/repository/branches/:branch/protect + put ":id/repository/branches/:branch/protect" do + authorize_admin_project + + @branch = user_project.repository.find_branch(params[:branch]) + not_found! unless @branch + protected_branch = user_project.protected_branches.find_by(name: @branch.name) + user_project.protected_branches.create(name: @branch.name) unless protected_branch + + present @branch, with: Entities::RepoObject, project: user_project + end + + # Unprotect a single branch + # + # Parameters: + # id (required) - The ID of a project + # branch (required) - The name of the branch + # Example Request: + # PUT /projects/:id/repository/branches/:branch/unprotect + put ":id/repository/branches/:branch/unprotect" do + authorize_admin_project + + @branch = user_project.repository.find_branch(params[:branch]) + not_found! unless @branch + protected_branch = user_project.protected_branches.find_by(name: @branch.name) + protected_branch.destroy if protected_branch + + present @branch, with: Entities::RepoObject, project: user_project + end + + # Create branch + # + # Parameters: + # id (required) - The ID of a project + # branch_name (required) - The name of the branch + # ref (required) - Create branch from commit sha or existing branch + # Example Request: + # POST /projects/:id/repository/branches + post ":id/repository/branches" do + authorize_push_project + @branch = CreateBranchService.new.execute(user_project, params[:branch_name], params[:ref], current_user) + + present @branch, with: Entities::RepoObject, project: user_project + end + end + end +end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 33b8b3d2244..4a67313430a 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -1,21 +1,12 @@ require 'mime/types' module API - # Projects API + # Projects commits API class Commits < Grape::API before { authenticate! } before { authorize! :download_code, user_project } resource :projects do - helpers do - def handle_project_member_errors(errors) - if errors[:project_access].any? - error!(errors[:project_access], 422) - end - not_found! - end - end - # Get a project repository commits # # Parameters: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index fc309f65a56..7ee4b9d1381 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -56,8 +56,12 @@ module API end end - def paginate(object) - object.page(params[:page]).per(params[:per_page].to_i) + def paginate(relation) + per_page = params[:per_page].to_i + paginated = relation.page(params[:page]).per(per_page) + add_pagination_headers(paginated, per_page) + + paginated end def authenticate! @@ -74,6 +78,10 @@ module API end end + def authorize_push_project + authorize! :push_code, user_project + end + def authorize_admin_project authorize! :admin_project, user_project end @@ -134,6 +142,18 @@ module API private + def add_pagination_headers(paginated, per_page) + request_url = request.url.split('?').first + + links = [] + links << %(<#{request_url}?page=#{paginated.current_page - 1}&per_page=#{per_page}>; rel="prev") unless paginated.first_page? + links << %(<#{request_url}?page=#{paginated.current_page + 1}&per_page=#{per_page}>; rel="next") unless paginated.last_page? + links << %(<#{request_url}?page=1&per_page=#{per_page}>; rel="first") + links << %(<#{request_url}?page=#{paginated.total_pages}&per_page=#{per_page}>; rel="last") + + header 'Link', links.join(', ') + end + def abilities @abilities ||= begin abilities = Six.new diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index ba53bf9baa4..076a9ceeb74 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -15,66 +15,6 @@ module API not_found! end end - - # Get a project repository branches - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/branches - get ":id/repository/branches" do - present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project - end - - # Get a single branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # GET /projects/:id/repository/branches/:branch - get ":id/repository/branches/:branch" do - @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } - not_found!("Branch does not exist") if @branch.nil? - present @branch, with: Entities::RepoObject, project: user_project - end - - # Protect a single branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # PUT /projects/:id/repository/branches/:branch/protect - put ":id/repository/branches/:branch/protect" do - authorize_admin_project - - @branch = user_project.repository.find_branch(params[:branch]) - not_found! unless @branch - protected_branch = user_project.protected_branches.find_by(name: @branch.name) - user_project.protected_branches.create(name: @branch.name) unless protected_branch - - present @branch, with: Entities::RepoObject, project: user_project - end - - # Unprotect a single branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # PUT /projects/:id/repository/branches/:branch/unprotect - put ":id/repository/branches/:branch/unprotect" do - authorize_admin_project - - @branch = user_project.repository.find_branch(params[:branch]) - not_found! unless @branch - protected_branch = user_project.protected_branches.find_by(name: @branch.name) - protected_branch.destroy if protected_branch - - present @branch, with: Entities::RepoObject, project: user_project - end - # Get a project repository tags # # Parameters: @@ -161,7 +101,7 @@ module API repo = user_project.repository ref = params[:sha] format = params[:format] - storage_path = Rails.root.join("tmp", "repositories") + storage_path = Gitlab.config.gitlab.repository_downloads_path file_path = repo.archive_repo(ref, storage_path, format) if file_path && File.exists?(file_path) diff --git a/lib/backup/database.rb b/lib/backup/database.rb index 6552f45ff0b..7b6908ccad8 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -29,9 +29,10 @@ module Backup print "Restoring MySQL database #{config['database']} ... " system('mysql', *mysql_args, config['database'], in: db_file_name) when "postgresql" then - puts "Destructively rebuilding database schema for RAILS_ENV #{Rails.env}" - Rake::Task["db:schema:load"].invoke print "Restoring PostgreSQL database #{config['database']} ... " + # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE + # statements like MySQL. + Rake::Task["gitlab:db:drop_all_tables"].invoke pg_env system('psql', config['database'], '-f', db_file_name) end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 456a61b9e43..01d86430f02 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -81,16 +81,17 @@ module Gitlab private + def find_by_uid_and_provider + find_by_uid(uid) + end + def find_by_uid(uid) - model.where(provider: provider, extern_uid: uid).last + # LDAP distinguished name is case-insensitive + model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last end def username - (auth.info.nickname || samaccountname).to_s.force_encoding("utf-8") - end - - def samaccountname - (auth.extra[:raw_info][:samaccountname] || []).first + auth.info.nickname.to_s.force_encoding("utf-8") end def provider diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 427e6d697e5..3dd4465a6d8 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -149,7 +149,7 @@ exit_if_not_running(){ } ## Starts Unicorn and Sidekiq if they're not running. -start() { +start_gitlab() { check_stale_pids if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then @@ -167,7 +167,7 @@ start() { # Remove old socket if it exists rm -f "$socket_path"/gitlab.socket 2>/dev/null # Start the web server - RAILS_ENV=$RAILS_ENV script/web start & + RAILS_ENV=$RAILS_ENV script/web start fi # If sidekiq is already running, don't start it again. @@ -184,7 +184,7 @@ start() { } ## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them. -stop() { +stop_gitlab() { exit_if_not_running if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then @@ -246,7 +246,7 @@ print_status() { } ## Tells unicorn to reload it's config and Sidekiq to restart -reload(){ +reload_gitlab(){ exit_if_not_running if [ "$wpid" = "0" ];then echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded." @@ -263,12 +263,12 @@ reload(){ } ## Restarts Sidekiq and Unicorn. -restart(){ +restart_gitlab(){ check_status if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then - stop + stop_gitlab fi - start + start_gitlab } @@ -276,16 +276,16 @@ restart(){ case "$1" in start) - start + start_gitlab ;; stop) - stop + stop_gitlab ;; restart) - restart + restart_gitlab ;; reload|force-reload) - reload + reload_gitlab ;; status) print_status diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 071760c0c36..3b9b2531bf7 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -677,7 +677,20 @@ namespace :gitlab do end def filter - Net::LDAP::Filter.present?(ldap_config.uid) + uid_filter = Net::LDAP::Filter.present?(ldap_config.uid) + if user_filter + Net::LDAP::Filter.join(uid_filter, user_filter) + else + uid_filter + end + end + + def user_filter + if ldap_config['user_filter'] && ldap_config.user_filter.present? + Net::LDAP::Filter.construct(ldap_config.user_filter) + else + nil + end end def ldap diff --git a/lib/tasks/gitlab/db/drop_all_tables.rake b/lib/tasks/gitlab/db/drop_all_tables.rake new file mode 100644 index 00000000000..a66030ab93a --- /dev/null +++ b/lib/tasks/gitlab/db/drop_all_tables.rake @@ -0,0 +1,10 @@ +namespace :gitlab do + namespace :db do + task drop_all_tables: :environment do + connection = ActiveRecord::Base.connection + connection.tables.each do |table| + connection.drop_table(table) + end + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index fd8d7133ae9..fef6314f23a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -292,6 +292,20 @@ describe User do end end + describe 'search' do + let(:user1) { create(:user, username: 'James', email: 'james@testing.com') } + let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') } + + it "should be case insensitive" do + User.search(user1.username.upcase).to_a.should == [user1] + User.search(user1.username.downcase).to_a.should == [user1] + User.search(user2.username.upcase).to_a.should == [user2] + User.search(user2.username.downcase).to_a.should == [user2] + User.search(user1.username.downcase).to_a.count.should == 2 + User.search(user2.username.downcase).to_a.count.should == 1 + end + end + describe 'by_username_or_id' do let(:user1) { create(:user, username: 'foo') } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb new file mode 100644 index 00000000000..f792c618e67 --- /dev/null +++ b/spec/requests/api/branches_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' +require 'mime/types' + +describe API::API do + include ApiHelpers + before(:each) { enable_observers } + after(:each) {disable_observers} + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:project) { create(:project, creator_id: user.id) } + let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } + let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) } + + describe "GET /projects/:id/repository/branches" do + it "should return an array of project branches" do + get api("/projects/#{project.id}/repository/branches", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name + end + end + + describe "GET /projects/:id/repository/branches/:branch" do + it "should return the branch information for a single branch" do + get api("/projects/#{project.id}/repository/branches/new_design", user) + response.status.should == 200 + + json_response['name'].should == 'new_design' + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' + json_response['protected'].should == false + end + + it "should return a 403 error if guest" do + get api("/projects/#{project.id}/repository/branches", user2) + response.status.should == 403 + end + + it "should return a 404 error if branch is not available" do + get api("/projects/#{project.id}/repository/branches/unknown", user) + response.status.should == 404 + end + end + + describe "PUT /projects/:id/repository/branches/:branch/protect" do + it "should protect a single branch" do + put api("/projects/#{project.id}/repository/branches/new_design/protect", user) + response.status.should == 200 + + json_response['name'].should == 'new_design' + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' + json_response['protected'].should == true + end + + it "should return a 404 error if branch not found" do + put api("/projects/#{project.id}/repository/branches/unknown/protect", user) + response.status.should == 404 + end + + it "should return a 403 error if guest" do + put api("/projects/#{project.id}/repository/branches/new_design/protect", user2) + response.status.should == 403 + end + + it "should return success when protect branch again" do + put api("/projects/#{project.id}/repository/branches/new_design/protect", user) + put api("/projects/#{project.id}/repository/branches/new_design/protect", user) + response.status.should == 200 + end + end + + describe "PUT /projects/:id/repository/branches/:branch/unprotect" do + it "should unprotect a single branch" do + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user) + response.status.should == 200 + + json_response['name'].should == 'new_design' + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' + json_response['protected'].should == false + end + + it "should return success when unprotect branch" do + put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) + response.status.should == 404 + end + + it "should return success when unprotect branch again" do + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user) + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user) + response.status.should == 200 + end + end + + + describe "POST /projects/:id/repository/branches" do + it "should create a new branch" do + post api("/projects/#{project.id}/repository/branches", user), + branch_name: 'new_design', + ref: '621491c677087aa243f165eab467bfdfbee00be1' + + response.status.should == 201 + + json_response['name'].should == 'new_design' + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' + end + + it "should deny for user without push access" do + post api("/projects/#{project.id}/repository/branches", user2), + branch_name: 'new_design', + ref: '621491c677087aa243f165eab467bfdfbee00be1' + + response.status.should == 403 + end + end +end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index e9422cd2643..0b4f22bfb3d 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -25,6 +25,12 @@ describe API::API do json_response.should be_an Array json_response.first['title'].should == issue.title end + + it "should add pagination headers" do + get api("/issues?per_page=3", user) + response.headers['Link'].should == + '; rel="first", ; rel="last"' + end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 99d966edc38..44c561eab58 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -14,86 +14,6 @@ describe API::API do before { project.team << [user, :reporter] } - - describe "GET /projects/:id/repository/branches" do - it "should return an array of project branches" do - get api("/projects/#{project.id}/repository/branches", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name - end - end - - describe "GET /projects/:id/repository/branches/:branch" do - it "should return the branch information for a single branch" do - get api("/projects/#{project.id}/repository/branches/new_design", user) - response.status.should == 200 - - json_response['name'].should == 'new_design' - json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' - json_response['protected'].should == false - end - - it "should return a 403 error if guest" do - get api("/projects/#{project.id}/repository/branches", user2) - response.status.should == 403 - end - - it "should return a 404 error if branch is not available" do - get api("/projects/#{project.id}/repository/branches/unknown", user) - response.status.should == 404 - end - end - - describe "PUT /projects/:id/repository/branches/:branch/protect" do - it "should protect a single branch" do - put api("/projects/#{project.id}/repository/branches/new_design/protect", user) - response.status.should == 200 - - json_response['name'].should == 'new_design' - json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' - json_response['protected'].should == true - end - - it "should return a 404 error if branch not found" do - put api("/projects/#{project.id}/repository/branches/unknown/protect", user) - response.status.should == 404 - end - - it "should return a 403 error if guest" do - put api("/projects/#{project.id}/repository/branches/new_design/protect", user2) - response.status.should == 403 - end - - it "should return success when protect branch again" do - put api("/projects/#{project.id}/repository/branches/new_design/protect", user) - put api("/projects/#{project.id}/repository/branches/new_design/protect", user) - response.status.should == 200 - end - end - - describe "PUT /projects/:id/repository/branches/:branch/unprotect" do - it "should unprotect a single branch" do - put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user) - response.status.should == 200 - - json_response['name'].should == 'new_design' - json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' - json_response['protected'].should == false - end - - it "should return success when unprotect branch" do - put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) - response.status.should == 404 - end - - it "should return success when unprotect branch again" do - put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user) - put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user) - response.status.should == 200 - end - end - describe "GET /projects/:id/repository/tags" do it "should return an array of project tags" do get api("/projects/#{project.id}/repository/tags", user)