From 5f682094d9b7c985ad62ebe29664bb6fe87b54be Mon Sep 17 00:00:00 2001 From: Matthew Monaco Date: Wed, 18 Jun 2014 11:49:39 -0600 Subject: [PATCH 001/109] Add 'confirm' option to users api --- CHANGELOG | 1 + doc/api/users.md | 1 + lib/api/users.rb | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5dab8c864e..5298e137d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 7.5.0 - Tie up loose ends with annotated tags: API & UI (Sean Edge) - Return valid json for deleting branch via API (sponsored by O'Reilly Media) - Expose username in project events API (sponsored by O'Reilly Media) + - Allow user confirmation to be skipped for new users via API v 7.4.2 - Fix internal snippet exposing for unauthenticated users diff --git a/doc/api/users.md b/doc/api/users.md index 3fdd3a75e8..fec5deebee 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -168,6 +168,7 @@ Parameters: - `bio` (optional) - User's biography - `admin` (optional) - User is admin - true or false (default) - `can_create_group` (optional) - User can create groups - true or false +- `confirm` (optional) - Require confirmation - true (default) or false ## User modification diff --git a/lib/api/users.rb b/lib/api/users.rb index d07815a8a9..1a4a8535d4 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -54,15 +54,18 @@ module API # bio - Bio # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false + # confirm - Require user confirmation - true (default) or false # Example Request: # POST /users post do authenticated_as_admin! required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :confirm, :admin] user = User.build_user(attrs) admin = attrs.delete(:admin) user.admin = admin unless admin.nil? + confirm = ! (attrs.delete(:confirm) =~ (/(false|f|no|0)$/i)) + user.skip_confirmation! unless confirm if user.save present user, with: Entities::UserFull else From d0f6654e9ced38d03f5946fde10c3f0e3732a5d7 Mon Sep 17 00:00:00 2001 From: Alexander Balashov Date: Tue, 18 Nov 2014 10:12:05 +0300 Subject: [PATCH 002/109] Remove useless `assets.compress` option, Rails 4 uses only `assets.js_compressor` > The config.assets.compress option should be changed to config.assets.js_compressor like so for instance --- config/environments/production.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 78bf543402..3316ece387 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -11,8 +11,9 @@ Gitlab::Application.configure do # Disable Rails's static asset server (Apache or nginx will already do this) config.serve_static_assets = false - # Compress JavaScripts and CSS - config.assets.compress = true + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass # Don't fallback to assets pipeline if a precompiled asset is missed config.assets.compile = true @@ -74,7 +75,6 @@ Gitlab::Application.configure do config.action_mailer.raise_delivery_errors = true config.eager_load = true - config.assets.js_compressor = :uglifier config.allow_concurrency = false end From d37cf2a23da0fcbaca695092822e9cd2af4b1bdb Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sun, 19 Oct 2014 10:02:13 +0200 Subject: [PATCH 003/109] Factor permission check in issuable finder --- app/finders/issuable_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index e147751006..088a766ed3 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -44,7 +44,7 @@ class IssuableFinder table_name = klass.table_name if project - if project.public? || (current_user && current_user.can?(:read_project, project)) + if Ability.abilities.allowed?(current_user, :read_project, project) project.send(table_name) else [] From a3a66939cacd7ffd6aa806fdb0d973ab7c1eb09c Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sun, 8 Feb 2015 23:27:51 -0800 Subject: [PATCH 004/109] formatting cleanup on mysql_to_postgresql.md --- doc/update/mysql_to_postgresql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 229689392b..8e65cb3390 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -75,7 +75,7 @@ test -e /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb.bundled || sudo / ## Converting a GitLab backup file from MySQL to Postgres **Note:** Please make sure to have Python 2.7.x (or higher) installed. -GitLab backup files (_gitlab_backup.tar) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server. +GitLab backup files (`_gitlab_backup.tar`) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server. ``` # Stop GitLab From 692bb679d0ade7401e6a422e2faeb04b63bd23c2 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sun, 8 Feb 2015 23:51:37 -0800 Subject: [PATCH 005/109] use gitlab branch of mysql-postgresql-converter --- doc/update/mysql_to_postgresql.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 229689392b..ba8259d582 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -11,7 +11,7 @@ sudo service gitlab stop # Update /home/git/gitlab/config/database.yml -git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd mysql-postgresql-converter mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production -p python db_converter.py databasename.mysql databasename.psql @@ -38,7 +38,7 @@ On non-omnibus installations (distributed using Git) we retrieve the index decla ``` # Clone the database converter on your Postgres-backed GitLab server cd /tmp -git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd /home/git/gitlab @@ -59,7 +59,7 @@ On omnibus-gitlab we need to get the index declarations from a file called `sche ``` # Clone the database converter on your Postgres-backed GitLab server cd /tmp -/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd /tmp/mysql-postgresql-converter # Download schema.rb.bundled if necessary @@ -97,7 +97,7 @@ cd tmp/backups/postgresql sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p # Clone the database converter -sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab # Convert gitlabhq_production.mysql sudo -u git -H mkdir db From 252ee4e7e51ae18f30f4b9089be850daaca958ac Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 11 Aug 2014 11:06:15 -0700 Subject: [PATCH 006/109] Improve login screen when only OmniAuth providers are enabled Avoids an empty Sign in box when signup_enabled? is false, and avoids showing "No authentication methods configured" unless there really are none. OmniAuth signin gets its own file for consistency with signin and signup and LDAP. --- app/views/devise/sessions/new.html.haml | 16 ++++++++++++++-- app/views/devise/shared/_omniauth_box.html.haml | 10 ++++++++++ app/views/devise/shared/_signin_box.html.haml | 16 ---------------- 3 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 app/views/devise/shared/_omniauth_box.html.haml diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index fa2460518f..89e4e229ac 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,6 +1,18 @@ %div - = render 'devise/shared/signin_box' + - if signin_enabled? || ldap_enabled? + = render 'devise/shared/signin_box' - - if signup_enabled? + -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box + - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? + .clearfix.prepend-top-20 + = render 'devise/shared/omniauth_box' + + -# Signup only makes sense if you can also sign-in + - if signin_enabled? && signup_enabled? .prepend-top-20 = render 'devise/shared/signup_box' + + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?) + %div + No authentication methods configured. diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml new file mode 100644 index 0000000000..4cd1c303b2 --- /dev/null +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -0,0 +1,10 @@ +%p + %span.light + Sign in with   + - providers = additional_providers + - providers.each do |provider| + %span.light + - if default_providers.include?(provider) + = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider) + - else + = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn" diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 805cf81623..8faa6398a6 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -24,19 +24,3 @@ - elsif signin_enabled? = render 'devise/sessions/new_base' - - else - %div - No authentication methods configured. - -- if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? - .clearfix.prepend-top-20 - %p - %span.light - Sign in with   - - providers = additional_providers - - providers.each do |provider| - %span.light - - if default_providers.include?(provider) - = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider) - - else - = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn" \ No newline at end of file From b86caf0de317cfc4012f10d30a2e15eef471948e Mon Sep 17 00:00:00 2001 From: Bugagazavr Date: Mon, 16 Feb 2015 02:10:47 +0300 Subject: [PATCH 007/109] Correct json payload [ci skip] --- doc/api/repositories.md | 61 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 8acf85d21c..3316745380 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -15,24 +15,21 @@ Parameters: ```json [ { - "name": "v1.0.0", "commit": { - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "parents": [], - "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d", - "message": "Initial commit", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "Jack Smith", - "email": "jack@example.com" - }, + "author_name": "John Smith", + "author_email": "john@example.com", "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00" + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] }, - "protected": null + "name": "v1.0.0", + "message": null } ] ``` @@ -53,23 +50,23 @@ Parameters: - `message` (optional) - Creates annotated tag. ```json -[ - { - "name": "v1.0.0", - "message": "Release 1.0.0", - "commit": { - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "parents": [], - "message": "Initial commit", - "authored_date": "2012-05-28T04:42:42-07:00", - "author_name": "John Smith", - "author email": "john@example.com", - "committer_name": "Jack Smith", - "committed_date": "2012-05-28T04:42:42-07:00", - "committer_email": "jack@example.com" - }, - } -] +{ + "commit": { + "author_name": "John Smith", + "author_email": "john@example.com", + "authored_date": "2012-05-28T04:42:42-07:00", + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] + }, + "name": "v1.0.0", + "message": null +} ``` The message will be `nil` when creating a lightweight tag otherwise it will contain the annotation. From 9bf8480b4a0d3ea6e284c4bd8bf26243f3f3f6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Rosen=C3=B6gger?= <123haynes@gmail.com> Date: Sat, 14 Feb 2015 16:04:45 +0100 Subject: [PATCH 008/109] Generalize the image upload in markdown This commit generalizes the image upload via drag and drop so it supports all files. It also adds access control for these files. --- CHANGELOG | 1 + .../javascripts/dropzone_input.js.coffee | 21 +++-- app/controllers/files_controller.rb | 29 ++++++- app/controllers/projects_controller.rb | 18 ++--- app/services/projects/file_service.rb | 55 +++++++++++++ app/services/projects/image_service.rb | 39 --------- app/uploaders/file_uploader.rb | 19 ++++- app/views/projects/_issuable_form.html.haml | 2 +- app/views/projects/issues/_form.html.haml | 2 +- .../projects/merge_requests/_form.html.haml | 2 +- .../merge_requests/_new_submit.html.haml | 5 +- app/views/projects/milestones/_form.html.haml | 4 +- app/views/projects/notes/_edit_form.html.haml | 2 +- app/views/projects/notes/_form.html.haml | 6 +- app/views/projects/wikis/_form.html.haml | 5 +- config/routes.rb | 5 +- spec/controllers/projects_controller_spec.rb | 48 ++++++----- spec/services/projects/file_service_spec.rb | 81 +++++++++++++++++++ spec/services/projects/image_service_spec.rb | 62 -------------- 19 files changed, 247 insertions(+), 159 deletions(-) create mode 100644 app/services/projects/file_service.rb delete mode 100644 app/services/projects/image_service.rb create mode 100644 spec/services/projects/file_service_spec.rb delete mode 100644 spec/services/projects/image_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 05d1e7bdb4..9e50178d8b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ v 7.8.0 (unreleased) - Fix broken access control for note attachments (Hannes Rosenögger) + - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) - Replace highlight.js with rouge-fork rugments (Stefan Tatschner) - Make project search case insensitive (Hannes Rosenögger) - Include issue/mr participants in list of recipients for reassign/close/reopen emails diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index d98d548293..bed8471b39 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -9,7 +9,7 @@ class @DropzoneInput iconPicture = "" iconSpinner = "" btnAlert = "" - project_image_path_upload = window.project_image_path_upload or null + project_file_path_upload = window.project_file_path_upload or null form_textarea = $(form).find("textarea.markdown-area") form_textarea.wrap "
" @@ -72,13 +72,12 @@ class @DropzoneInput form.find(".md-preview-holder").hide() dropzone = form_dropzone.dropzone( - url: project_image_path_upload + url: project_file_path_upload dictDefaultMessage: "" clickable: true - paramName: "markdown_img" + paramName: "markdown_file" maxFilesize: 10 uploadMultiple: false - acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png" headers: "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") @@ -133,7 +132,10 @@ class @DropzoneInput child = $(dropzone[0]).children("textarea") formatLink = (str) -> - "![" + str.alt + "](" + str.url + ")" + text = "[" + str.alt + "](" + str.url + ")" + if str.is_image is true + text = "!" + text + text handlePaste = (event) -> pasteEvent = event.originalEvent @@ -177,9 +179,9 @@ class @DropzoneInput uploadFile = (item, filename) -> formData = new FormData() - formData.append "markdown_img", item, filename + formData.append "markdown_file", item, filename $.ajax - url: project_image_path_upload + url: project_file_path_upload type: "POST" data: formData dataType: "json" @@ -234,4 +236,7 @@ class @DropzoneInput return formatLink: (str) -> - "![" + str.alt + "](" + str.url + ")" + text = "[" + str.alt + "](" + str.url + ")" + if str.is_image is true + text = "!" + text + text diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index 15523cbc2e..a86340dd9b 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -1,5 +1,5 @@ class FilesController < ApplicationController - def download + def download_notes note = Note.find(params[:id]) uploader = note.attachment @@ -14,7 +14,32 @@ class FilesController < ApplicationController not_found! end else - redirect_to uploader.url + not_found! end end + + def download_files + namespace_id = params[:namespace] + project_id = params[:project] + folder_id = params[:folder_id] + filename = params[:filename] + project_with_namespace="#{namespace_id}/#{project_id}" + filename_with_id="#{folder_id}/#{filename}" + + project = Project.find_with_namespace(project_with_namespace) + + uploader = FileUploader.new("#{Rails.root}/uploads","#{project_with_namespace}/#{folder_id}") + uploader.retrieve_from_store!(filename) + + if can?(current_user, :read_project, project) + download(uploader) + else + not_found! + end + end + + def download(uploader) + disposition = uploader.image? ? 'inline' : 'attachment' + send_file uploader.file.path, disposition: disposition + end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 462ab3d474..b430278903 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -134,12 +134,13 @@ class ProjectsController < ApplicationController end end - def upload_image - link_to_image = ::Projects::ImageService.new(repository, params, root_url).execute + def upload_file + link_to_file = ::Projects::FileService.new(repository, params, root_url). + execute respond_to do |format| - if link_to_image - format.json { render json: { link: link_to_image } } + if link_to_file + format.json { render json: { link: link_to_file } } else format.json { render json: 'Invalid file.', status: :unprocessable_entity } end @@ -158,13 +159,8 @@ class ProjectsController < ApplicationController private - def upload_path - base_dir = FileUploader.generate_dir - File.join(repository.path_with_namespace, base_dir) - end - - def accepted_images - %w(png jpg jpeg gif) + def invalid_file(error) + render json: { message: error.message }, status: :internal_server_error end def set_title diff --git a/app/services/projects/file_service.rb b/app/services/projects/file_service.rb new file mode 100644 index 0000000000..8c149bf53a --- /dev/null +++ b/app/services/projects/file_service.rb @@ -0,0 +1,55 @@ +module Projects + class FileService < BaseService + include Rails.application.routes.url_helpers + def initialize(repository, params, root_url) + @repository, @params, @root_url = repository, params.dup, root_url + end + + def execute + uploader = FileUploader.new("#{Rails.root}/uploads", upload_path, accepted_files) + file = @params['markdown_file'] + + if file + alt = file.original_filename + uploader.store!(file) + filename = nil + if image?(file) + filename=File.basename(alt, '.*') + else + filename=File.basename(alt) + end + link = { + 'alt' => filename, + 'url' => uploader.secure_url, + 'is_image' => image?(file) + } + else + link = nil + end + end + + protected + + def accepted_files + # insert accepted mime types here (e.g %w(jpg jpeg gif png)) + nil + end + + def accepted_images + %w(jpg jpeg gif png) + end + + def image?(file) + accepted_images.map { |format| file.content_type.include? format }.any? + end + + def upload_path + base_dir = FileUploader.generate_dir + File.join(@repository.path_with_namespace, base_dir) + end + + def correct_mime_type?(file) + accepted_files.map { |format| image.content_type.include? format }.any? + end + end +end diff --git a/app/services/projects/image_service.rb b/app/services/projects/image_service.rb deleted file mode 100644 index 7ca7e82c4a..0000000000 --- a/app/services/projects/image_service.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Projects - class ImageService < BaseService - include Rails.application.routes.url_helpers - def initialize(repository, params, root_url) - @repository, @params, @root_url = repository, params.dup, root_url - end - - def execute - uploader = FileUploader.new('uploads', upload_path, accepted_images) - image = @params['markdown_img'] - - if image && correct_mime_type?(image) - alt = image.original_filename - uploader.store!(image) - link = { - 'alt' => File.basename(alt, '.*'), - 'url' => File.join(@root_url, uploader.url) - } - else - link = nil - end - end - - protected - - def upload_path - base_dir = FileUploader.generate_dir - File.join(@repository.path_with_namespace, base_dir) - end - - def accepted_images - %w(png jpg jpeg gif) - end - - def correct_mime_type?(image) - accepted_images.map{ |format| image.content_type.include? format }.any? - end - end -end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 0fa987c93f..ac7bd5b27e 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -21,7 +21,7 @@ class FileUploader < CarrierWave::Uploader::Base end def extension_white_list - @allowed_extensions + @allowed_extensions || super end def store!(file) @@ -38,4 +38,21 @@ class FileUploader < CarrierWave::Uploader::Base def self.generate_dir SecureRandom.hex(5) end + + def secure_url + Gitlab.config.gitlab.relative_url_root + "/files/#{@path}/#{@filename}" + end + + def image? + img_ext = %w(png jpg jpeg gif bmp tiff) + if file.respond_to?(:extension) + img_ext.include?(file.extension.downcase) + else + # Not all CarrierWave storages respond to :extension + ext = file.path.split('.').last.downcase + img_ext.include?(ext) + end + rescue + false + end end diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 5a57673b58..18897b055a 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -23,7 +23,7 @@ Parsed with #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}. .pull-right - Attach images (JPG, PNG, GIF) by dragging & dropping + Attach files by dragging & dropping or #{link_to 'selecting them', '#', class: 'markdown-selector' }. .clearfix diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 2a7b44955c..975980bd6b 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -11,4 +11,4 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_file_path_upload = "#{upload_file_project_path @project}"; diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index d52e64666a..28c4734e14 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -9,4 +9,4 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_file_path_upload = "#{upload_file_project_path @project}"; diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index bca3e45bf1..0653b30fcc 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -27,7 +27,7 @@ Parsed with #{link_to 'Gitlab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}. .pull-right - Attach images (JPG, PNG, GIF) by dragging & dropping + Attach files by dragging & dropping or #{link_to 'selecting them', '#', class: 'markdown-selector'}. .clearfix @@ -113,10 +113,11 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_file_path_upload = "#{upload_file_project_path @project}"; :javascript var merge_request merge_request = new MergeRequest({ action: 'commits' }); + diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index b3b170d711..5fbb668570 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -25,7 +25,7 @@ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' .hint .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .clearfix .error-alert .col-md-6 @@ -51,4 +51,4 @@ onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_file_path_upload = "#{upload_file_project_path @project}"; diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index cdc76f5d96..4ba5907831 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -6,7 +6,7 @@ .comment-hints.clearfix .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. .note-form-actions .buttons diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 1a4e06289f..fe3dab569f 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f| += form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" }, authenticity_token: true do |f| = note_target_fields = f.hidden_field :commit_id = f.hidden_field :line_code @@ -11,7 +11,7 @@ .comment-hints.clearfix .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. .note-form-actions @@ -29,4 +29,4 @@ = f.file_field :attachment, class: "js-note-attachment-input hidden" :javascript - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_file_path_upload = "#{upload_file_project_path @project}"; diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 84731e43e9..0afee138c8 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -26,7 +26,7 @@ = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' .col-sm-12.hint .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .clearfix .error-alert @@ -43,5 +43,6 @@ = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel" :javascript - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_file_path_upload = "#{upload_file_project_path @project}"; + diff --git a/config/routes.rb b/config/routes.rb index 65786d8356..bd659ddeab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -93,7 +93,8 @@ Gitlab::Application.routes.draw do # # Attachments serving # - get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ } + get 'files/:type/:id/:filename' => 'files#download_notes', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ } + get 'files/:namespace/:project/:folder_id/:filename' => 'files#download_files', constraints: { namespace: /[^\/]+/, project: /[a-zA-Z.\/0-9_\-]+/, filename: /.+/ } # # Admin Area @@ -220,7 +221,7 @@ Gitlab::Application.routes.draw do put :transfer post :archive post :unarchive - post :upload_image + post :upload_file post :toggle_star post :markdown_preview get :autocomplete_sources diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index ef786ccd32..039751a41e 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -7,43 +7,49 @@ describe ProjectsController do let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } - describe "POST #upload_image" do + describe 'POST #upload_file' do before do sign_in(user) project.team << [user, :developer] end - context "without params['markdown_img']" do - it "returns an error" do - post :upload_image, id: project.to_param, format: :json + context "without params['markdown_file']" do + it 'returns an error' do + post :upload_file, id: project.to_param, format: :json expect(response.status).to eq(422) end end - context "with invalid file" do + context 'with valid image' do before do - post :upload_image, id: project.to_param, markdown_img: txt, format: :json + post :upload_file, + id: project.to_param, + markdown_file: jpg, + format: :json end - it "returns an error" do - expect(response.status).to eq(422) - end - end - - context "with valid file" do - before do - post :upload_image, id: project.to_param, markdown_img: jpg, format: :json - end - - it "returns a content with original filename and new link." do - expect(response.body).to match "\"alt\":\"rails_sample\"" + it 'returns a content with original filename, new link, and correct type.' do + expect(response.body).to match '\"alt\":\"rails_sample\"' expect(response.body).to match "\"url\":\"http://test.host/uploads/#{project.path_with_namespace}" + expect(response.body).to match '\"is_image\":true' + end + end + + context 'with valid non-image file' do + before do + post :upload_file, id: project.to_param, markdown_file: txt, format: :json + end + + it 'returns a content with original filename, new link, and correct type.' do + expect(response.body).to match '\"alt\":\"doc_sample.txt\"' + expect(response.body).to match "\"url\":\"http://test.host/uploads/#{project.path_with_namespace}" + expect(response.body).to match '\"is_image\":false' end end end - describe "POST #toggle_star" do - it "toggles star if user is signed in" do + describe 'POST #toggle_star' do + it 'toggles star if user is signed in' do sign_in(user) expect(user.starred?(public_project)).to be_falsey post :toggle_star, id: public_project.to_param @@ -52,7 +58,7 @@ describe ProjectsController do expect(user.starred?(public_project)).to be_falsey end - it "does nothing if user is not signed in" do + it 'does nothing if user is not signed in' do post :toggle_star, id: public_project.to_param expect(user.starred?(public_project)).to be_falsey post :toggle_star, id: public_project.to_param diff --git a/spec/services/projects/file_service_spec.rb b/spec/services/projects/file_service_spec.rb new file mode 100644 index 0000000000..38ab4a467b --- /dev/null +++ b/spec/services/projects/file_service_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe Projects::FileService do + describe 'File service' do + before do + @user = create :user + @project = create :project, creator_id: @user.id, namespace: @user.namespace + end + + context 'for valid gif file' do + before do + gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') + @link_to_file = upload_file(@project.repository, + { 'markdown_file' => gif }, + 'http://test.example/') + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('banana_sample') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("http://test.example/uploads/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('banana_sample.gif') } + end + + context 'for valid png file' do + before do + png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', + 'image/png') + @link_to_file = upload_file(@project.repository, + { 'markdown_file' => png }, + 'http://test.example/') + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_value('dk') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("http://test.example/uploads/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('dk.png') } + end + + context 'for valid jpg file' do + before do + jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') + @link_to_file = upload_file(@project.repository, { 'markdown_file' => jpg }, 'http://test.example/') + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('rails_sample') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("http://test.example/uploads/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('rails_sample.jpg') } + end + + context 'for txt file' do + before do + txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') + @link_to_file = upload_file(@project.repository, + { 'markdown_file' => txt }, + 'http://test.example/') + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('doc_sample.txt') } + it { expect(@link_to_file['is_image']).to equal(false) } + it { expect(@link_to_file['url']).to match("http://test.example/uploads/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('doc_sample.txt') } + end + end + + def upload_file(repository, params, root_url) + Projects::FileService.new(repository, params, root_url).execute + end +end diff --git a/spec/services/projects/image_service_spec.rb b/spec/services/projects/image_service_spec.rb deleted file mode 100644 index 23c4e227ae..0000000000 --- a/spec/services/projects/image_service_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'spec_helper' - -describe Projects::ImageService do - describe 'Image service' do - before do - @user = create :user - @project = create :project, creator_id: @user.id, namespace: @user.namespace - end - - context 'for valid gif file' do - before do - gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => gif }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("banana_sample") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("banana_sample.gif") } - end - - context 'for valid png file' do - before do - png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => png }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("dk") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("dk.png") } - end - - context 'for valid jpg file' do - before do - jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => jpg }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("rails_sample") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("rails_sample.jpg") } - end - - context 'for txt file' do - before do - txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => txt }, "http://test.example/") - end - - it { expect(@link_to_image).to be_nil } - end - end - - def upload_image(repository, params, root_url) - Projects::ImageService.new(repository, params, root_url).execute - end -end From ca504a77fe994da893cd0632de5e0e7ea5b729fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Rosen=C3=B6gger?= <123haynes@gmail.com> Date: Sat, 14 Feb 2015 16:45:22 +0100 Subject: [PATCH 009/109] Fix tests --- spec/services/projects/file_service_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/services/projects/file_service_spec.rb b/spec/services/projects/file_service_spec.rb index 38ab4a467b..e2d6766735 100644 --- a/spec/services/projects/file_service_spec.rb +++ b/spec/services/projects/file_service_spec.rb @@ -20,7 +20,7 @@ describe Projects::FileService do it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_value('banana_sample') } it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("http://test.example/uploads/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match("/files/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('banana_sample.gif') } end @@ -38,7 +38,7 @@ describe Projects::FileService do it { expect(@link_to_file).to have_value('dk') } it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("http://test.example/uploads/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match("/files/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('dk.png') } end @@ -53,7 +53,7 @@ describe Projects::FileService do it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_value('rails_sample') } it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("http://test.example/uploads/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match("/files/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('rails_sample.jpg') } end @@ -70,7 +70,7 @@ describe Projects::FileService do it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_value('doc_sample.txt') } it { expect(@link_to_file['is_image']).to equal(false) } - it { expect(@link_to_file['url']).to match("http://test.example/uploads/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match("/files/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('doc_sample.txt') } end end From 9729cc584f5758395960416f308a9c45f698cdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Rosen=C3=B6gger?= <123haynes@gmail.com> Date: Sat, 14 Feb 2015 19:52:45 +0100 Subject: [PATCH 010/109] implement Project::UploadsController --- app/controllers/files_controller.rb | 29 ++----------------- .../projects/uploads_controller.rb | 16 ++++++++++ app/uploaders/file_uploader.rb | 4 ++- config/routes.rb | 5 ++-- spec/services/projects/file_service_spec.rb | 8 ++--- 5 files changed, 28 insertions(+), 34 deletions(-) create mode 100644 app/controllers/projects/uploads_controller.rb diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index a86340dd9b..15523cbc2e 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -1,5 +1,5 @@ class FilesController < ApplicationController - def download_notes + def download note = Note.find(params[:id]) uploader = note.attachment @@ -14,32 +14,7 @@ class FilesController < ApplicationController not_found! end else - not_found! + redirect_to uploader.url end end - - def download_files - namespace_id = params[:namespace] - project_id = params[:project] - folder_id = params[:folder_id] - filename = params[:filename] - project_with_namespace="#{namespace_id}/#{project_id}" - filename_with_id="#{folder_id}/#{filename}" - - project = Project.find_with_namespace(project_with_namespace) - - uploader = FileUploader.new("#{Rails.root}/uploads","#{project_with_namespace}/#{folder_id}") - uploader.retrieve_from_store!(filename) - - if can?(current_user, :read_project, project) - download(uploader) - else - not_found! - end - end - - def download(uploader) - disposition = uploader.image? ? 'inline' : 'attachment' - send_file uploader.file.path, disposition: disposition - end end diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb new file mode 100644 index 0000000000..1c9fb1c86f --- /dev/null +++ b/app/controllers/projects/uploads_controller.rb @@ -0,0 +1,16 @@ +class Projects::UploadsController < Projects::ApplicationController + layout 'project' + + before_filter :project + + def show + folder_id = params[:folder_id] + filename = params[:filename] + + uploader = FileUploader.new("#{Rails.root}/uploads","#{@project.path_with_namespace}/#{folder_id}") + uploader.retrieve_from_store!(filename) + + disposition = uploader.image? ? 'inline' : 'attachment' + send_file uploader.file.path, disposition: disposition + end +end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index ac7bd5b27e..51ae8040e5 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -40,7 +40,9 @@ class FileUploader < CarrierWave::Uploader::Base end def secure_url - Gitlab.config.gitlab.relative_url_root + "/files/#{@path}/#{@filename}" + path_array = @path.split('/') + path = File.join(path_array[0],path_array[1],'uploads',path_array[2]) + Gitlab.config.gitlab.relative_url_root + "/#{path}/#{@filename}" end def image? diff --git a/config/routes.rb b/config/routes.rb index bd659ddeab..d29ad8db63 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -93,8 +93,7 @@ Gitlab::Application.routes.draw do # # Attachments serving # - get 'files/:type/:id/:filename' => 'files#download_notes', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ } - get 'files/:namespace/:project/:folder_id/:filename' => 'files#download_files', constraints: { namespace: /[^\/]+/, project: /[a-zA-Z.\/0-9_\-]+/, filename: /.+/ } + get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ } # # Admin Area @@ -257,6 +256,8 @@ Gitlab::Application.routes.draw do end end + get '/uploads/:folder_id/:filename' => 'uploads#show', constraints: { filename: /.+/ } + get '/compare/:from...:to' => 'compare#show', :as => 'compare', :constraints => { from: /.+/, to: /.+/ } diff --git a/spec/services/projects/file_service_spec.rb b/spec/services/projects/file_service_spec.rb index e2d6766735..7bbe5b575c 100644 --- a/spec/services/projects/file_service_spec.rb +++ b/spec/services/projects/file_service_spec.rb @@ -20,7 +20,7 @@ describe Projects::FileService do it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_value('banana_sample') } it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/files/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('banana_sample.gif') } end @@ -38,7 +38,7 @@ describe Projects::FileService do it { expect(@link_to_file).to have_value('dk') } it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/files/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('dk.png') } end @@ -53,7 +53,7 @@ describe Projects::FileService do it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_value('rails_sample') } it { expect(@link_to_file['is_image']).to equal(true) } - it { expect(@link_to_file['url']).to match("/files/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('rails_sample.jpg') } end @@ -70,7 +70,7 @@ describe Projects::FileService do it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_value('doc_sample.txt') } it { expect(@link_to_file['is_image']).to equal(false) } - it { expect(@link_to_file['url']).to match("/files/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('doc_sample.txt') } end end From 192e7306626e073a5e6fb3b41d69f0e3fddb0821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Rosen=C3=B6gger?= <123haynes@gmail.com> Date: Sun, 15 Feb 2015 18:48:32 +0100 Subject: [PATCH 011/109] Fix tests --- spec/controllers/projects_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 039751a41e..2d52e3fd91 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -30,7 +30,7 @@ describe ProjectsController do it 'returns a content with original filename, new link, and correct type.' do expect(response.body).to match '\"alt\":\"rails_sample\"' - expect(response.body).to match "\"url\":\"http://test.host/uploads/#{project.path_with_namespace}" + expect(response.body).to match "\"url\":\"/#{project.path_with_namespace}/uploads" expect(response.body).to match '\"is_image\":true' end end @@ -42,7 +42,7 @@ describe ProjectsController do it 'returns a content with original filename, new link, and correct type.' do expect(response.body).to match '\"alt\":\"doc_sample.txt\"' - expect(response.body).to match "\"url\":\"http://test.host/uploads/#{project.path_with_namespace}" + expect(response.body).to match "\"url\":\"/#{project.path_with_namespace}/uploads" expect(response.body).to match '\"is_image\":false' end end From d2ebdf664b42d4fac6b2e060ef79aa9fe0b0e72d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 16 Feb 2015 19:58:40 +0100 Subject: [PATCH 012/109] Refactor. --- .gitignore | 1 + Gemfile | 1 + Gemfile.lock | 6 ++ .../javascripts/dropzone_input.js.coffee | 26 ++++----- app/controllers/files_controller.rb | 17 +++--- .../projects/uploads_controller.rb | 39 ++++++++++--- app/controllers/projects_controller.rb | 17 ------ app/services/projects/file_service.rb | 55 ------------------- app/services/projects/upload_service.rb | 22 ++++++++ app/uploaders/attachment_uploader.rb | 2 +- app/uploaders/file_uploader.rb | 38 ++++--------- app/views/projects/issues/_form.html.haml | 2 +- .../projects/merge_requests/_form.html.haml | 2 +- .../merge_requests/_new_submit.html.haml | 2 +- app/views/projects/milestones/_form.html.haml | 2 +- app/views/projects/notes/_form.html.haml | 2 +- app/views/projects/wikis/_form.html.haml | 2 +- config/routes.rb | 7 ++- db/schema.rb | 1 - .../projects/uploads_controller_spec.rb | 49 +++++++++++++++++ spec/controllers/projects_controller_spec.rb | 45 +-------------- ...service_spec.rb => upload_service_spec.rb} | 20 +++---- 22 files changed, 164 insertions(+), 194 deletions(-) delete mode 100644 app/services/projects/file_service.rb create mode 100644 app/services/projects/upload_service.rb create mode 100644 spec/controllers/projects/uploads_controller_spec.rb rename spec/services/projects/{file_service_spec.rb => upload_service_spec.rb} (80%) diff --git a/.gitignore b/.gitignore index 7a7b5c9393..89fe301ee8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ nohup.out public/assets/ public/uploads.* public/uploads/ +uploads/ rails_best_practices_output.html tags tmp/ diff --git a/Gemfile b/Gemfile index c3d8299e94..3f0eae8ef4 100644 --- a/Gemfile +++ b/Gemfile @@ -205,6 +205,7 @@ group :development do gem "letter_opener" gem 'quiet_assets', '~> 1.0.1' gem 'rack-mini-profiler', require: false + gem "byebug" # Better errors handler gem 'better_errors' diff --git a/Gemfile.lock b/Gemfile.lock index 3283da40f8..2f5ebf80b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -61,6 +61,9 @@ GEM sass (~> 3.2) browser (0.7.2) builder (3.2.2) + byebug (3.2.0) + columnize (~> 0.8) + debugger-linecache (~> 1.2) cal-heatmap-rails (0.0.1) capybara (2.2.1) mime-types (>= 1.16) @@ -88,6 +91,7 @@ GEM coffee-script-source (1.6.3) colored (1.2) colorize (0.5.8) + columnize (0.9.0) connection_pool (2.1.0) coveralls (0.7.0) multi_json (~> 1.3) @@ -103,6 +107,7 @@ GEM daemons (1.1.9) database_cleaner (1.3.0) debug_inspector (0.0.2) + debugger-linecache (1.2.0) default_value_for (3.0.0) activerecord (>= 3.2.0, < 5.0) descendants_tracker (0.0.3) @@ -646,6 +651,7 @@ DEPENDENCIES binding_of_caller bootstrap-sass (~> 3.0) browser + byebug cal-heatmap-rails (~> 0.0.1) capybara (~> 2.2.1) carrierwave diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index bed8471b39..2d9b496b13 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -9,7 +9,7 @@ class @DropzoneInput iconPicture = "" iconSpinner = "" btnAlert = "" - project_file_path_upload = window.project_file_path_upload or null + project_uploads_path = window.project_uploads_path or null form_textarea = $(form).find("textarea.markdown-area") form_textarea.wrap "
" @@ -72,10 +72,10 @@ class @DropzoneInput form.find(".md-preview-holder").hide() dropzone = form_dropzone.dropzone( - url: project_file_path_upload + url: project_uploads_path dictDefaultMessage: "" clickable: true - paramName: "markdown_file" + paramName: "file" maxFilesize: 10 uploadMultiple: false headers: @@ -131,10 +131,9 @@ class @DropzoneInput child = $(dropzone[0]).children("textarea") - formatLink = (str) -> - text = "[" + str.alt + "](" + str.url + ")" - if str.is_image is true - text = "!" + text + formatLink = (link) -> + text = "[#{link.alt}](#{link.url})" + text = "!#{text}" if link.is_image text handlePaste = (event) -> @@ -179,9 +178,9 @@ class @DropzoneInput uploadFile = (item, filename) -> formData = new FormData() - formData.append "markdown_file", item, filename + formData.append "file", item, filename $.ajax - url: project_file_path_upload + url: project_uploads_path type: "POST" data: formData dataType: "json" @@ -235,8 +234,7 @@ class @DropzoneInput $(@).closest('.gfm-form').find('.div-dropzone').click() return - formatLink: (str) -> - text = "[" + str.alt + "](" + str.url + ")" - if str.is_image is true - text = "!" + text - text + formatLink: (link) -> + text = "[#{link.alt}](#{link.url})" + text = "!#{text}" if link.is_image + text \ No newline at end of file diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index 15523cbc2e..267239b7b8 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -3,18 +3,21 @@ class FilesController < ApplicationController note = Note.find(params[:id]) uploader = note.attachment - if uploader.file_storage? - if can?(current_user, :read_project, note.project) - # Replace old notes location in /public with the new one in / and send the file + if can?(current_user, :read_project, note.project) + if uploader.file_storage? path = uploader.file.path.gsub("#{Rails.root}/public", Rails.root.to_s) - disposition = uploader.image? ? 'inline' : 'attachment' - send_file path, disposition: disposition + if File.exist?(path) + disposition = uploader.image? ? 'inline' : 'attachment' + send_file path, disposition: disposition + else + not_found! + end else - not_found! + redirect_to uploader.url end else - redirect_to uploader.url + not_found! end end end diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index 1c9fb1c86f..355163ac87 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -3,14 +3,37 @@ class Projects::UploadsController < Projects::ApplicationController before_filter :project - def show - folder_id = params[:folder_id] - filename = params[:filename] - - uploader = FileUploader.new("#{Rails.root}/uploads","#{@project.path_with_namespace}/#{folder_id}") - uploader.retrieve_from_store!(filename) + def create + link_to_file = ::Projects::UploadService.new(repository, params[:file]). + execute - disposition = uploader.image? ? 'inline' : 'attachment' - send_file uploader.file.path, disposition: disposition + respond_to do |format| + if link_to_file + format.json do + render json: { link: link_to_file } + end + else + format.json do + render json: 'Invalid file.', status: :unprocessable_entity + end + end + end + end + + def show + uploader = FileUploader.new(project, params[:secret]) + + if uploader.file_storage? + uploader.retrieve_from_store!(params[:filename]) + + if uploader.file.exists? + disposition = uploader.image? ? 'inline' : 'attachment' + send_file uploader.file.path, disposition: disposition + else + not_found! + end + else + redirect_to uploader.url + end end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b430278903..9be66b6b9f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -134,19 +134,6 @@ class ProjectsController < ApplicationController end end - def upload_file - link_to_file = ::Projects::FileService.new(repository, params, root_url). - execute - - respond_to do |format| - if link_to_file - format.json { render json: { link: link_to_file } } - else - format.json { render json: 'Invalid file.', status: :unprocessable_entity } - end - end - end - def toggle_star current_user.toggle_star(@project) @project.reload @@ -159,10 +146,6 @@ class ProjectsController < ApplicationController private - def invalid_file(error) - render json: { message: error.message }, status: :internal_server_error - end - def set_title @title = 'New Project' end diff --git a/app/services/projects/file_service.rb b/app/services/projects/file_service.rb deleted file mode 100644 index 8c149bf53a..0000000000 --- a/app/services/projects/file_service.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Projects - class FileService < BaseService - include Rails.application.routes.url_helpers - def initialize(repository, params, root_url) - @repository, @params, @root_url = repository, params.dup, root_url - end - - def execute - uploader = FileUploader.new("#{Rails.root}/uploads", upload_path, accepted_files) - file = @params['markdown_file'] - - if file - alt = file.original_filename - uploader.store!(file) - filename = nil - if image?(file) - filename=File.basename(alt, '.*') - else - filename=File.basename(alt) - end - link = { - 'alt' => filename, - 'url' => uploader.secure_url, - 'is_image' => image?(file) - } - else - link = nil - end - end - - protected - - def accepted_files - # insert accepted mime types here (e.g %w(jpg jpeg gif png)) - nil - end - - def accepted_images - %w(jpg jpeg gif png) - end - - def image?(file) - accepted_images.map { |format| file.content_type.include? format }.any? - end - - def upload_path - base_dir = FileUploader.generate_dir - File.join(@repository.path_with_namespace, base_dir) - end - - def correct_mime_type?(file) - accepted_files.map { |format| image.content_type.include? format }.any? - end - end -end diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb new file mode 100644 index 0000000000..a186c97628 --- /dev/null +++ b/app/services/projects/upload_service.rb @@ -0,0 +1,22 @@ +module Projects + class UploadService < BaseService + def initialize(project, file) + @project, @file = project, file + end + + def execute + return nil unless @file + + uploader = FileUploader.new(@project) + uploader.store!(@file) + + filename = uploader.image? ? uploader.file.basename : uploader.file.filename + + { + 'alt' => filename, + 'url' => uploader.secure_url, + 'is_image' => uploader.image? + } + end + end +end diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index 22742d287a..58dc6e90c1 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -21,7 +21,7 @@ class AttachmentUploader < CarrierWave::Uploader::Base end def secure_url - Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" + File.join(Gitlab.config.gitlab.relative_url_root, "files", model.class.to_s.underscore, model.id.to_s, file.filename) end def file_storage? diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 51ae8040e5..c040f6bbe9 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -2,47 +2,33 @@ class FileUploader < CarrierWave::Uploader::Base storage :file - def initialize(base_dir, path = '', allowed_extensions = nil) - @base_dir = base_dir - @path = path - @allowed_extensions = allowed_extensions + def initialize(project, secret = self.class.generate_secret) + @project = project + @secret = secret end def base_dir - @base_dir + "#{Rails.root}/uploads" end def store_dir - File.join(@base_dir, @path) + File.join(base_dir, @project.path_with_namespace, @secret) end def cache_dir - File.join(@base_dir, 'tmp', @path) + File.join(base_dir, 'tmp', @project.path_with_namespace, @secret) end - def extension_white_list - @allowed_extensions || super - end - - def store!(file) - @filename = self.class.generate_filename(file) - super - end - - def self.generate_filename(file) - original_filename = File.basename(file.original_filename, '.*') - extension = File.extname(file.original_filename) - new_filename = Digest::MD5.hexdigest(original_filename) + extension - end - - def self.generate_dir + def self.generate_secret SecureRandom.hex(5) end def secure_url - path_array = @path.split('/') - path = File.join(path_array[0],path_array[1],'uploads',path_array[2]) - Gitlab.config.gitlab.relative_url_root + "/#{path}/#{@filename}" + File.join(Gitlab.config.gitlab.relative_url_root, @project.path_with_namespace, "uploads", @secret, file.filename) + end + + def file_storage? + self.class.storage == CarrierWave::Storage::File end def image? diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 975980bd6b..afeeed6edf 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -11,4 +11,4 @@ e.preventDefault(); }); - window.project_file_path_upload = "#{upload_file_project_path @project}"; + window.project_uploads_path = "#{project_uploads_path @project}"; diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 28c4734e14..c1a05e4586 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -9,4 +9,4 @@ e.preventDefault(); }); - window.project_file_path_upload = "#{upload_file_project_path @project}"; + window.project_uploads_path = "#{project_uploads_path @project}"; diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 0653b30fcc..4cf2a05b1a 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -113,7 +113,7 @@ e.preventDefault(); }); - window.project_file_path_upload = "#{upload_file_project_path @project}"; + window.project_uploads_path = "#{project_uploads_path @project}"; :javascript var merge_request diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 5fbb668570..dbcd23eee0 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -51,4 +51,4 @@ onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); - window.project_file_path_upload = "#{upload_file_project_path @project}"; + window.project_uploads_path = "#{project_uploads_path @project}"; diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index fe3dab569f..9f9efc782d 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -29,4 +29,4 @@ = f.file_field :attachment, class: "js-note-attachment-input hidden" :javascript - window.project_file_path_upload = "#{upload_file_project_path @project}"; + window.project_uploads_path = "#{project_uploads_path @project}"; diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 0afee138c8..b1579878ed 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -43,6 +43,6 @@ = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel" :javascript - window.project_file_path_upload = "#{upload_file_project_path @project}"; + window.project_uploads_path = "#{project_uploads_path @project}"; diff --git a/config/routes.rb b/config/routes.rb index d29ad8db63..f0a7cf1e8a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -220,7 +220,6 @@ Gitlab::Application.routes.draw do put :transfer post :archive post :unarchive - post :upload_file post :toggle_star post :markdown_preview get :autocomplete_sources @@ -256,7 +255,11 @@ Gitlab::Application.routes.draw do end end - get '/uploads/:folder_id/:filename' => 'uploads#show', constraints: { filename: /.+/ } + resources :uploads, only: [:create] do + collection do + get ":secret/:filename", action: :show, constraints: { filename: /.+/ } + end + end get '/compare/:from...:to' => 'compare#show', :as => 'compare', :constraints => { from: /.+/, to: /.+/ } diff --git a/db/schema.rb b/db/schema.rb index e11a068c9c..be3d35a431 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -26,7 +26,6 @@ ActiveRecord::Schema.define(version: 20150213121042) do t.datetime "updated_at" t.string "home_page_url" t.integer "default_branch_protection", default: 2 - t.boolean "twitter_sharing_enabled", default: true end create_table "broadcast_messages", force: true do |t| diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb new file mode 100644 index 0000000000..8c99b5ca52 --- /dev/null +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -0,0 +1,49 @@ +require('spec_helper') + +describe Projects::UploadsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } + + describe 'POST #create' do + before do + sign_in(user) + project.team << [user, :developer] + end + + context "without params['file']" do + it 'returns an error' do + post :create, project_id: project.to_param, format: :json + expect(response.status).to eq(422) + end + end + + context 'with valid image' do + before do + post :create, + project_id: project.to_param, + file: jpg, + format: :json + end + + it 'returns a content with original filename, new link, and correct type.' do + expect(response.body).to match '\"alt\":\"rails_sample\"' + expect(response.body).to match "\"url\":\"/#{project.path_with_namespace}/uploads" + expect(response.body).to match '\"is_image\":true' + end + end + + context 'with valid non-image file' do + before do + post :create, project_id: project.to_param, file: txt, format: :json + end + + it 'returns a content with original filename, new link, and correct type.' do + expect(response.body).to match '\"alt\":\"doc_sample.txt\"' + expect(response.body).to match "\"url\":\"/#{project.path_with_namespace}/uploads" + expect(response.body).to match '\"is_image\":false' + end + end + end +end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 2d52e3fd91..9be4c2e505 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -4,50 +4,7 @@ describe ProjectsController do let(:project) { create(:project) } let(:public_project) { create(:project, :public) } let(:user) { create(:user) } - let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } - let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } - - describe 'POST #upload_file' do - before do - sign_in(user) - project.team << [user, :developer] - end - - context "without params['markdown_file']" do - it 'returns an error' do - post :upload_file, id: project.to_param, format: :json - expect(response.status).to eq(422) - end - end - - context 'with valid image' do - before do - post :upload_file, - id: project.to_param, - markdown_file: jpg, - format: :json - end - - it 'returns a content with original filename, new link, and correct type.' do - expect(response.body).to match '\"alt\":\"rails_sample\"' - expect(response.body).to match "\"url\":\"/#{project.path_with_namespace}/uploads" - expect(response.body).to match '\"is_image\":true' - end - end - - context 'with valid non-image file' do - before do - post :upload_file, id: project.to_param, markdown_file: txt, format: :json - end - - it 'returns a content with original filename, new link, and correct type.' do - expect(response.body).to match '\"alt\":\"doc_sample.txt\"' - expect(response.body).to match "\"url\":\"/#{project.path_with_namespace}/uploads" - expect(response.body).to match '\"is_image\":false' - end - end - end - + describe 'POST #toggle_star' do it 'toggles star if user is signed in' do sign_in(user) diff --git a/spec/services/projects/file_service_spec.rb b/spec/services/projects/upload_service_spec.rb similarity index 80% rename from spec/services/projects/file_service_spec.rb rename to spec/services/projects/upload_service_spec.rb index 7bbe5b575c..fc34b45648 100644 --- a/spec/services/projects/file_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::FileService do +describe Projects::UploadService do describe 'File service' do before do @user = create :user @@ -10,9 +10,7 @@ describe Projects::FileService do context 'for valid gif file' do before do gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') - @link_to_file = upload_file(@project.repository, - { 'markdown_file' => gif }, - 'http://test.example/') + @link_to_file = upload_file(@project.repository, gif) end it { expect(@link_to_file).to have_key('alt') } @@ -28,9 +26,7 @@ describe Projects::FileService do before do png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') - @link_to_file = upload_file(@project.repository, - { 'markdown_file' => png }, - 'http://test.example/') + @link_to_file = upload_file(@project.repository, png) end it { expect(@link_to_file).to have_key('alt') } @@ -45,7 +41,7 @@ describe Projects::FileService do context 'for valid jpg file' do before do jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') - @link_to_file = upload_file(@project.repository, { 'markdown_file' => jpg }, 'http://test.example/') + @link_to_file = upload_file(@project.repository, jpg) end it { expect(@link_to_file).to have_key('alt') } @@ -60,9 +56,7 @@ describe Projects::FileService do context 'for txt file' do before do txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') - @link_to_file = upload_file(@project.repository, - { 'markdown_file' => txt }, - 'http://test.example/') + @link_to_file = upload_file(@project.repository, txt) end it { expect(@link_to_file).to have_key('alt') } @@ -75,7 +69,7 @@ describe Projects::FileService do end end - def upload_file(repository, params, root_url) - Projects::FileService.new(repository, params, root_url).execute + def upload_file(repository, file) + Projects::UploadService.new(repository, file).execute end end From ab401a6132411294cee03cf4e0902ec75c2c42dc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 16 Feb 2015 22:08:44 +0100 Subject: [PATCH 013/109] Remove note attachment file selector. --- app/assets/javascripts/notes.js.coffee | 13 ------------- app/views/projects/notes/_edit_form.html.haml | 10 +--------- app/views/projects/notes/_form.html.haml | 8 -------- 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 1c090bd06d..90e6fd6d15 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -39,9 +39,6 @@ class @Notes # reset main target form after submit $(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm - # attachment button - $(document).on "click", ".js-choose-note-attachment-button", @chooseNoteAttachment - # update the file name when an attachment is selected $(document).on "change", ".js-note-attachment-input", @updateFormAttachment @@ -73,7 +70,6 @@ class @Notes $(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-attachment-delete" $(document).off "ajax:complete", ".js-main-target-form" - $(document).off "click", ".js-choose-note-attachment-button" $(document).off "click", ".js-discussion-reply-button" $(document).off "click", ".js-add-diff-note-button" $(document).off "visibilitychange" @@ -173,15 +169,6 @@ class @Notes form.find(".js-note-text").data("autosave").reset() - ### - Called when clicking the "Choose File" button. - - Opens the file selection dialog. - ### - chooseNoteAttachment: -> - form = $(this).closest("form") - form.find(".js-note-attachment-input").click() - ### Shows the main form and does some setup on it. diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index 4ba5907831..ca097f3d55 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -11,12 +11,4 @@ .note-form-actions .buttons = f.submit 'Save Comment', class: "btn btn-primary btn-save btn-grouped js-comment-button" - = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" - - .note-form-option.hidden-xs - %a.choose-btn.btn.js-choose-note-attachment-button - %i.fa.fa-paperclip - %span Choose File ... -   - %span.file_name.js-attachment-filename - = f.file_field :attachment, class: "js-note-attachment-input hidden" + = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" \ No newline at end of file diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 9f9efc782d..8b331ef819 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -20,13 +20,5 @@ = yield(:note_actions) %a.btn.grouped.js-close-discussion-note-form Cancel - .note-form-option.hidden-xs - %a.choose-btn.btn.js-choose-note-attachment-button - %i.fa.fa-paperclip - %span Choose File ... -   - %span.file_name.js-attachment-filename - = f.file_field :attachment, class: "js-note-attachment-input hidden" - :javascript window.project_uploads_path = "#{project_uploads_path @project}"; From ab65be7a2f5d46a681d882f4f90d1f0438c64b02 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 16 Feb 2015 22:08:55 +0100 Subject: [PATCH 014/109] Use longer upload secret. --- app/uploaders/file_uploader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index c040f6bbe9..bdfbfc668b 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -20,7 +20,7 @@ class FileUploader < CarrierWave::Uploader::Base end def self.generate_secret - SecureRandom.hex(5) + SecureRandom.hex end def secure_url From 99fb4d387fbcddc56292b788a84b1e62d27765dd Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 16 Feb 2015 22:09:17 +0100 Subject: [PATCH 015/109] Add paperclip icon to links to uploads in notes. --- app/assets/stylesheets/sections/notes.scss | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 5494845eb8..40adc8b3ba 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -66,6 +66,22 @@ ul.notes { overflow: auto; word-wrap: break-word; @include md-typography; + + a[href*="/uploads/"] { + &:before { + margin-right: 4px; + + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + content: "\f0c6"; + } + + &:hover:before { + text-decoration: none; + } + } } } .note-header { From 4036fb167ddce47db8ad4d885000b2c9db96595d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 16 Feb 2015 22:09:52 +0100 Subject: [PATCH 016/109] Change textarea upload hover icon from picture to paperclip. --- app/assets/javascripts/dropzone_input.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index 2d9b496b13..06e9f0001a 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -6,7 +6,7 @@ class @DropzoneInput divHover = "
" divSpinner = "
" divAlert = "
" - iconPicture = "" + iconPaperclip = "" iconSpinner = "" btnAlert = "" project_uploads_path = window.project_uploads_path or null @@ -19,7 +19,7 @@ class @DropzoneInput form_dropzone = $(form).find('.div-dropzone') form_dropzone.parent().addClass "div-dropzone-wrapper" form_dropzone.append divHover - $(".div-dropzone-hover").append iconPicture + $(".div-dropzone-hover").append iconPaperclip form_dropzone.append divSpinner $(".div-dropzone-spinner").append iconSpinner $(".div-dropzone-spinner").css From 65b125a5035cb021aeb81e168fd4ae1ad6c74c11 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Feb 2015 08:16:42 +0100 Subject: [PATCH 017/109] Update schema. --- db/schema.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/schema.rb b/db/schema.rb index be3d35a431..e11a068c9c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -26,6 +26,7 @@ ActiveRecord::Schema.define(version: 20150213121042) do t.datetime "updated_at" t.string "home_page_url" t.integer "default_branch_protection", default: 2 + t.boolean "twitter_sharing_enabled", default: true end create_table "broadcast_messages", force: true do |t| From 008e3d66e9649079037260c072020340c69f3eec Mon Sep 17 00:00:00 2001 From: Standa Opichal Date: Wed, 18 Feb 2015 15:47:10 +0100 Subject: [PATCH 018/109] Fix for TeamCity buildQueue REST API build/@branchName. Strips the refs/heads/ if any to get just the branchName out of a :ref e.g.: `refs/heads/feature/newProfile` -> `feature/newProfile` The TeamCity buildQueue POST data require the branchName attribute to contain just the branch name (just xyzbranch and not the full ref in the refs/heads/xyzbranch). With the refs/heads/xyzbranch the build is triggered and looks like it is working however it always ompiles the default branch (or master). --- app/models/project_services/teamcity_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index c4b6ef5d9a..b6932f1c77 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -115,13 +115,13 @@ class TeamcityService < CiService end end - def execute(data) + def execute(push) auth = { username: username, password: password, } - branch = data[:ref] + branch = push[:ref].gsub('refs/heads/', '') self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", body: ""\ From 5493e27d53d157c1d43dca6c29815f4bc3a39c5c Mon Sep 17 00:00:00 2001 From: krolik Date: Fri, 20 Feb 2015 13:00:42 +0200 Subject: [PATCH 019/109] Fixed merge request diff page after back browser button is pressed. --- app/assets/javascripts/merge_request.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 757592842e..b891c1352b 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -123,7 +123,7 @@ class @MergeRequest loadDiff: (event) -> $.ajax type: 'GET' - url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json"; beforeSend: => this.$('.mr-loading-status .loading').show() complete: => From 51b234cfb6582650f362cf2339d0f8b0b5a10345 Mon Sep 17 00:00:00 2001 From: krolik Date: Fri, 20 Feb 2015 13:03:28 +0200 Subject: [PATCH 020/109] Removed exceeding semicolon --- app/assets/javascripts/merge_request.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index b891c1352b..09a7b4b310 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -123,7 +123,7 @@ class @MergeRequest loadDiff: (event) -> $.ajax type: 'GET' - url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json"; + url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json" beforeSend: => this.$('.mr-loading-status .loading').show() complete: => From 874640123b9b508fef40d4285a7c28d7e4653dd7 Mon Sep 17 00:00:00 2001 From: Derek Campbell Date: Fri, 20 Feb 2015 09:20:42 -0400 Subject: [PATCH 021/109] To close an issue you must set 'state_event' to 'close'. I cannot set 'closed' to '1'. --- doc/api/issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index 5a2f6a4c22..a7dd8b74c3 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -208,7 +208,7 @@ If an error occurs, an error number and a message explaining the reason is retur ## Delete existing issue (**Deprecated**) -The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `closed` set to 1. +The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `state_event` set to `close`. ``` DELETE /projects/:id/issues/:issue_id From c801df81fb48272b670b7448e3898a98cdb8b742 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 20 Feb 2015 14:39:35 +0100 Subject: [PATCH 022/109] Satisfy Rubocop. --- app/controllers/projects/uploads_controller.rb | 2 +- config/initializers/static_files.rb | 2 +- config/routes.rb | 4 ++-- lib/gitlab/middleware/static.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index b922b56418..2b4da35bc7 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -16,4 +16,4 @@ class Projects::UploadsController < Projects::ApplicationController not_found! end end -end \ No newline at end of file +end diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb index e04c29cee4..2a6eaec0cc 100644 --- a/config/initializers/static_files.rb +++ b/config/initializers/static_files.rb @@ -10,4 +10,4 @@ begin rescue # If ActionDispatch::Static wasn't loaded onto the stack (like in production), # an exception is raised. -end \ No newline at end of file +end diff --git a/config/routes.rb b/config/routes.rb index 0e7f7d893d..ca56c2d268 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -76,11 +76,11 @@ Gitlab::Application.routes.draw do scope path: :uploads do # Note attachments and User/Group/Project avatars get ":model/:mounted_as/:id/:filename", to: "uploads#show", - constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /.+/ } + constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /.+/ } # Project markdown uploads get ":id/:secret/:filename", to: "projects/uploads#show", - constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/, filename: /.+/ } + constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/, filename: /.+/ } end # diff --git a/lib/gitlab/middleware/static.rb b/lib/gitlab/middleware/static.rb index b92319c95d..85ffa8aca6 100644 --- a/lib/gitlab/middleware/static.rb +++ b/lib/gitlab/middleware/static.rb @@ -10,4 +10,4 @@ module Gitlab end end end -end \ No newline at end of file +end From 4ef6ffaad3e9b7a29b438722e5e101de78521ec7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 20 Feb 2015 15:19:50 +0100 Subject: [PATCH 023/109] Split up AttachmentUploader. --- app/models/group.rb | 2 +- app/models/project.rb | 2 +- app/models/user.rb | 2 +- app/uploaders/attachment_uploader.rb | 10 -------- app/uploaders/avatar_uploader.rb | 32 ++++++++++++++++++++++++ app/views/events/event/_note.html.haml | 6 ++--- app/views/projects/notes/_note.html.haml | 6 ++--- features/steps/groups.rb | 2 +- features/steps/profile/profile.rb | 2 +- features/steps/project/project.rb | 2 +- 10 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 app/uploaders/avatar_uploader.rb diff --git a/app/models/group.rb b/app/models/group.rb index d6ec0be608..da9621a2a1 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -23,7 +23,7 @@ class Group < Namespace validate :avatar_type, if: ->(user) { user.avatar_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } - mount_uploader :avatar, AttachmentUploader + mount_uploader :avatar, AvatarUploader after_create :post_create_hook after_destroy :post_destroy_hook diff --git a/app/models/project.rb b/app/models/project.rb index 56e1aa2904..e2c7f76eb0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -138,7 +138,7 @@ class Project < ActiveRecord::Base if: ->(project) { project.avatar && project.avatar_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } - mount_uploader :avatar, AttachmentUploader + mount_uploader :avatar, AvatarUploader # Scopes scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } diff --git a/app/models/user.rb b/app/models/user.rb index 21ccc76978..a723b1289b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -177,7 +177,7 @@ class User < ActiveRecord::Base end end - mount_uploader :avatar, AttachmentUploader + mount_uploader :avatar, AvatarUplaoder # Scopes scope :admins, -> { where(admin: true) } diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index b122b6c865..a9691bee46 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -3,8 +3,6 @@ class AttachmentUploader < CarrierWave::Uploader::Base storage :file - after :store, :reset_events_cache - def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end @@ -22,15 +20,7 @@ class AttachmentUploader < CarrierWave::Uploader::Base false end - def secure_url - Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" - end - def file_storage? self.class.storage == CarrierWave::Storage::File end - - def reset_events_cache(file) - model.reset_events_cache if model.is_a?(User) - end end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb new file mode 100644 index 0000000000..7cad044555 --- /dev/null +++ b/app/uploaders/avatar_uploader.rb @@ -0,0 +1,32 @@ +# encoding: utf-8 + +class AvatarUploader < CarrierWave::Uploader::Base + storage :file + + after :store, :reset_events_cache + + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + + def image? + img_ext = %w(png jpg jpeg gif bmp tiff) + if file.respond_to?(:extension) + img_ext.include?(file.extension.downcase) + else + # Not all CarrierWave storages respond to :extension + ext = file.path.split('.').last.downcase + img_ext.include?(ext) + end + rescue + false + end + + def file_storage? + self.class.storage == CarrierWave::Storage::File + end + + def reset_events_cache(file) + model.reset_events_cache if model.is_a?(User) + end +end diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index 0acb853877..4ef18c0906 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -18,9 +18,9 @@ - note = event.target - if note.attachment.url - if note.attachment.image? - = link_to note.attachment.secure_url, target: '_blank' do - = image_tag note.attachment.secure_url, class: 'note-image-attach' + = link_to note.attachment.url, target: '_blank' do + = image_tag note.attachment.url, class: 'note-image-attach' - else - = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do + = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do %i.fa.fa-paperclip = note.attachment_identifier diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 88c7b7ccf1..cfeba00d27 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -57,10 +57,10 @@ - if note.attachment.url .note-attachment - if note.attachment.image? - = link_to note.attachment.secure_url, target: '_blank' do - = image_tag note.attachment.secure_url, class: 'note-image-attach' + = link_to note.attachment.url, target: '_blank' do + = image_tag note.attachment.url, class: 'note-image-attach' .attachment - = link_to note.attachment.secure_url, target: "_blank" do + = link_to note.attachment.url, target: "_blank" do %i.fa.fa-paperclip = note.attachment_identifier = link_to delete_attachment_project_note_path(@project, note), diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 610e7fd3a4..0a9b4ccba5 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -110,7 +110,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I should see new group "Owned" avatar' do - Group.find_by(name: "Owned").avatar.should be_instance_of AttachmentUploader + Group.find_by(name: "Owned").avatar.should be_instance_of AvatarUploader Group.find_by(name: "Owned").avatar.url.should == "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png" end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index a907b0b7dc..4efd217678 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -29,7 +29,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step 'I should see new avatar' do - @user.avatar.should be_instance_of AttachmentUploader + @user.avatar.should be_instance_of AvatarUploader @user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png" end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 033d45e025..d39c8e7d2d 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -35,7 +35,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I should see new project avatar' do - @project.avatar.should be_instance_of AttachmentUploader + @project.avatar.should be_instance_of AvatarUploader url = @project.avatar.url url.should == "/uploads/project/avatar/#{ @project.id }/gitlab_logo.png" end From 7f1adc3d9cdc5c3f1c0fcbf6c72d89b8ee062af5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 20 Feb 2015 15:56:12 +0100 Subject: [PATCH 024/109] Fix URL to uploaded file. --- app/controllers/projects/uploads_controller.rb | 2 +- app/services/projects/upload_service.rb | 3 +-- app/uploaders/file_uploader.rb | 4 ++++ config/routes.rb | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index 53b92d8643..9020e86c44 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -4,7 +4,7 @@ class Projects::UploadsController < Projects::ApplicationController before_filter :project def create - link_to_file = ::Projects::UploadService.new(repository, params[:file]). + link_to_file = ::Projects::UploadService.new(project, params[:file]). execute respond_to do |format| diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb index b2466b52ad..a186c97628 100644 --- a/app/services/projects/upload_service.rb +++ b/app/services/projects/upload_service.rb @@ -1,6 +1,5 @@ module Projects class UploadService < BaseService - include Rails.application.routes.url_helpers def initialize(project, file) @project, @file = project, file end @@ -15,7 +14,7 @@ module Projects { 'alt' => filename, - 'url' => project_upload_url(@project, secret: uploader.secret, filename: uploader.file.filename), + 'url' => uploader.secure_url, 'is_image' => uploader.image? } end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 36a28f93c4..f9673abbfe 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -25,6 +25,10 @@ class FileUploader < CarrierWave::Uploader::Base SecureRandom.hex end + def secure_url + File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename) + end + def file_storage? self.class.storage == CarrierWave::Storage::File end diff --git a/config/routes.rb b/config/routes.rb index 498716b12e..b6f58acf1a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -79,8 +79,8 @@ Gitlab::Application.routes.draw do constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /.+/ } # Project markdown uploads - get ":id/:secret/:filename", to: "projects/uploads#show", - constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/, filename: /.+/ } + get ":project_id/:secret/:filename", to: "projects/uploads#show", + constraints: { project_id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/, filename: /.+/ } end # @@ -264,7 +264,7 @@ Gitlab::Application.routes.draw do resources :uploads, only: [:create] do collection do - get ":secret/:filename", action: :show, constraints: { filename: /.+/ } + get ":secret/:filename", action: :show, as: :show, constraints: { filename: /.+/ } end end From 2570e4df79fa09d3c4abc1d0ec82c67a322b249e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 20 Feb 2015 16:55:38 +0100 Subject: [PATCH 025/109] Fix specs. --- config/routes.rb | 10 ++++++---- spec/controllers/projects/uploads_controller_spec.rb | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index b6f58acf1a..3d826bf559 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -75,12 +75,14 @@ Gitlab::Application.routes.draw do scope path: :uploads do # Note attachments and User/Group/Project avatars - get ":model/:mounted_as/:id/:filename", to: "uploads#show", - constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /.+/ } + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /.+/ } # Project markdown uploads - get ":project_id/:secret/:filename", to: "projects/uploads#show", - constraints: { project_id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/, filename: /.+/ } + get ":project_id/:secret/:filename", + to: "projects/uploads#show", + constraints: { project_id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/, filename: /.+/ } end # diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 8c99b5ca52..28d313b7e9 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -29,7 +29,7 @@ describe Projects::UploadsController do it 'returns a content with original filename, new link, and correct type.' do expect(response.body).to match '\"alt\":\"rails_sample\"' - expect(response.body).to match "\"url\":\"/#{project.path_with_namespace}/uploads" + expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads" expect(response.body).to match '\"is_image\":true' end end @@ -41,7 +41,7 @@ describe Projects::UploadsController do it 'returns a content with original filename, new link, and correct type.' do expect(response.body).to match '\"alt\":\"doc_sample.txt\"' - expect(response.body).to match "\"url\":\"/#{project.path_with_namespace}/uploads" + expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads" expect(response.body).to match '\"is_image\":false' end end From b19b8c679a136234094443e2d4a345f136a0bcc1 Mon Sep 17 00:00:00 2001 From: Marco Wessel Date: Mon, 23 Feb 2015 02:31:12 +0100 Subject: [PATCH 026/109] Give last_activity_at a default value so it will always be set --- CHANGELOG | 1 + app/models/project.rb | 6 ++++++ .../20150223022001_set_missing_last_activity_at.rb | 9 +++++++++ db/schema.rb | 4 ++-- 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20150223022001_set_missing_last_activity_at.rb diff --git a/CHANGELOG b/CHANGELOG index 6702ba2ba4..5bdcc53540 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ v 7.9.0 (unreleased) - Upgrade Rails gem to version 4.1.9. - Improve UI for commits, issues and merge request lists - Fix commit comments on first line of diff not rendering in Merge Request Discussion view. + - Fix ordering of imported but unchanged projects (Marco Wessel) v 7.8.0 - Fix access control and protection against XSS for note attachments and other uploads. diff --git a/app/models/project.rb b/app/models/project.rb index 91ab788083..04189839d6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -48,6 +48,12 @@ class Project < ActiveRecord::Base default_value_for :wall_enabled, false default_value_for :snippets_enabled, gitlab_config_features.snippets + # set last_activity_at to the same as updated_at + before_create :set_last_activity_at + def set_last_activity_at + self.last_activity_at = self.updated_at + end + ActsAsTaggableOn.strict_case_match = true acts_as_taggable_on :tags diff --git a/db/migrate/20150223022001_set_missing_last_activity_at.rb b/db/migrate/20150223022001_set_missing_last_activity_at.rb new file mode 100644 index 0000000000..3a3adf1887 --- /dev/null +++ b/db/migrate/20150223022001_set_missing_last_activity_at.rb @@ -0,0 +1,9 @@ +class SetMissingLastActivityAt < ActiveRecord::Migration + def up + execute "UPDATE projects SET last_activity_at = updated_at WHERE last_activity_at IS NULL" + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index e11a068c9c..d34eab7508 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150213121042) do +ActiveRecord::Schema.define(version: 20150223022001) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -334,12 +334,12 @@ ActiveRecord::Schema.define(version: 20150213121042) do t.string "import_url" t.integer "visibility_level", default: 0, null: false t.boolean "archived", default: false, null: false - t.string "avatar" t.string "import_status" t.float "repository_size", default: 0.0 t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" + t.string "avatar" end add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree From d4bfdd34baa9eea2e89223b039f15cd4d4b3e5ae Mon Sep 17 00:00:00 2001 From: Marco Wessel Date: Mon, 23 Feb 2015 04:03:12 +0100 Subject: [PATCH 027/109] use update_column and set to created_at like elsewhere --- app/models/project.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 04189839d6..967e4de22a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -48,10 +48,10 @@ class Project < ActiveRecord::Base default_value_for :wall_enabled, false default_value_for :snippets_enabled, gitlab_config_features.snippets - # set last_activity_at to the same as updated_at - before_create :set_last_activity_at + # set last_activity_at to the same as created_at + after_create :set_last_activity_at def set_last_activity_at - self.last_activity_at = self.updated_at + update_column(:last_activity_at, self.created_at) end ActsAsTaggableOn.strict_case_match = true From 846f83177448832bd04ea0167b34541b5d3b71c6 Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Mon, 23 Feb 2015 10:40:06 -0800 Subject: [PATCH 028/109] Fixes grammatical consistency and small changes This commit adds consistency to small things like periods, commas, etc. Also gives additional information to buttons and headers. Fixes #2002, #2005, #2003 --- app/views/profiles/accounts/show.html.haml | 8 ++++---- app/views/profiles/applications.html.haml | 4 +++- app/views/profiles/design.html.haml | 4 ++-- app/views/profiles/emails/index.html.haml | 4 ++-- app/views/profiles/groups/index.html.haml | 4 ++-- app/views/profiles/history.html.haml | 4 ++-- app/views/profiles/keys/index.html.haml | 6 +++--- app/views/profiles/notifications/show.html.haml | 7 +++---- app/views/profiles/passwords/edit.html.haml | 4 ++-- app/views/profiles/show.html.haml | 4 ++-- 10 files changed, 25 insertions(+), 24 deletions(-) diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 53a50f6796..f124637c07 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,5 +1,5 @@ %h3.page-title - Account settings + Account Settings %p.light You can change your username and private token here. - if current_user.ldap_user? @@ -10,7 +10,7 @@ .account-page %fieldset.update-token %legend - Private token + Reset Private token %div = form_for @user, url: reset_private_token_profile_path, method: :put do |f| .data @@ -25,7 +25,7 @@ - if current_user.private_token = text_field_tag "token", current_user.private_token, class: "form-control" %div - = f.submit 'Reset', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token" + = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token" - else %span You don`t have one yet. Click generate to fix it. = f.submit 'Generate', class: "btn success btn-build-token" @@ -43,7 +43,7 @@ - if show_profile_username_tab? %fieldset.update-username %legend - Username + Change Username = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| %p Changing your username will change path to all personal projects! diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index 4b5817e10b..c8c522e981 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -1,5 +1,7 @@ %h3.page-title - OAuth2 + Application Settings +%p.light + OAuth2 protocol settings below. %fieldset.oauth-applications %legend Your applications diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml index 0d8075b7d4..8d09595fd4 100644 --- a/app/views/profiles/design.html.haml +++ b/app/views/profiles/design.html.haml @@ -1,7 +1,7 @@ %h3.page-title - My appearance settings + Design Settings %p.light - Appearance settings saved to your profile and available across all devices + Appearance settings will be saved to your profile and made available across all devices. %hr = form_for @user, url: profile_path, remote: true, method: :put do |f| diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 0b30e77233..3bbad6fdf7 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,5 +1,5 @@ %h3.page-title - My email addresses + Email Settings %p.light Your %b Primary Email @@ -34,4 +34,4 @@ .col-sm-10 = f.text_field :email, class: 'form-control' .form-actions - = f.submit 'Add', class: 'btn btn-create' + = f.submit 'Add email address', class: 'btn btn-create' diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml index e9ffca8faf..daf76636ff 100644 --- a/app/views/profiles/groups/index.html.haml +++ b/app/views/profiles/groups/index.html.haml @@ -1,12 +1,12 @@ %h3.page-title - Group membership + Group Membership - if current_user.can_create_group? %span.pull-right = link_to new_group_path, class: "btn btn-new" do %i.fa.fa-plus New Group %p.light - Group members have access to all a group's projects + Group members have access to all group projects. %hr .panel.panel-default .panel-heading diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml index 3951c47b5f..9cafe03b8b 100644 --- a/app/views/profiles/history.html.haml +++ b/app/views/profiles/history.html.haml @@ -1,7 +1,7 @@ %h3.page-title - Account history + My Account History %p.light - All events created by your account are listed here + All events created by your account are listed below. %hr .profile_history = render @events diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index c83c73ffcf..965d5e032f 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,12 +1,12 @@ %h3.page-title - My SSH keys (#{@keys.count}) + SSH Keys Settings .pull-right = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" %p.light - SSH keys allow you to establish a secure connection between your computer and GitLab + My SSH keys: #{@keys.count} %br Before you can add an SSH key you need to - = link_to "generate it", help_page_path("ssh", "README") + = link_to "generate it.", help_page_path("ssh", "README") %hr = render 'key_table' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 28bc5a426a..e3cd323927 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,10 +1,9 @@ %h3.page-title - Notifications settings + Notifications Settings %p.light These are your global notification settings. %hr - = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f| -if @user.errors.any? %div.alert.alert-danger @@ -60,7 +59,7 @@ %p You can also specify notification level per group or per project. %br - By default all projects and groups uses notification level set above. + By default, all projects and groups will use the notification level set above. %h4 Groups: %ul.bordered-list - @group_members.each do |users_group| @@ -69,7 +68,7 @@ .col-md-6 %p - To specify notification level per project of a group you belong to, + To specify the notification level per project of a group you belong to, %br you need to be a member of the project itself, not only its group. %h4 Projects: diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 6b19db4eb5..3b1ebbfaf5 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,4 +1,4 @@ -%h3.page-title Password +%h3.page-title Password Settings %p.light - if @user.password_automatically_set? Set your password. @@ -12,7 +12,7 @@ - unless @user.password_automatically_set? You must provide current password in order to change it. %br - After a successful password update you will be redirected to login page where you should login with your new password + After a successful password update, you will be redirected to the login page where you can log in with your new password. -if @user.errors.any? .alert.alert-danger %ul diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 640104fdad..b2808c46c0 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,7 +1,7 @@ %h3.page-title - Profile settings + Profile Settings %p.light - This information appears on your profile. + This information will appear on your profile. - if current_user.ldap_user? Some options are unavailable for LDAP accounts %hr From 5d2dda9744eb902e397410f8f4b94af3a0acb1df Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Mon, 23 Feb 2015 11:39:08 -0800 Subject: [PATCH 029/109] Added information to tooltips Tooltips now have meaning by mentioning their function ("Filter by..."). Fixes #1992 --- app/helpers/events_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 063916a8df..d38b546e1b 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -30,7 +30,7 @@ module EventsHelper end content_tag :li, class: "filter_icon #{active}" do - link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do + link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => 'Filter by ' + tooltip.downcase do icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) end end From 5ce2d44b136aae8e9e42397474a0e75bc6b32ded Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Mon, 23 Feb 2015 11:51:18 -0800 Subject: [PATCH 030/109] Added Profile tooltip For consistency sake, the profile in the navbar has a tooltip. --- app/views/layouts/_head_panel.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 77bfe4f996..d5928d2ed2 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -42,7 +42,7 @@ = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do %i.fa.fa-sign-out %li.hidden-xs - = link_to current_user, class: "profile-pic", id: 'profile-pic' do + = link_to current_user, class: "profile-pic has_bottom_tooltip", id: 'profile-pic', 'data-original-title' => 'Your profile' do = image_tag avatar_icon(current_user.email, 60), alt: 'User activity' = render 'shared/outdated_browser' From e35fe204795bbb19d46f34af49c1ad4f7148e68f Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Mon, 23 Feb 2015 13:38:04 -0800 Subject: [PATCH 031/109] Filter icons look like proper buttons The filter icons (Push events, Merge events, Comments, Team) now have a border that signifies they can be pushed. This not only keeps it in line with the rest of the application buttons, but it makes it more obvious, especially for the Merge events button with a checkbox icon. --- app/helpers/events_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 063916a8df..db0d4a2661 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -30,7 +30,7 @@ module EventsHelper end content_tag :li, class: "filter_icon #{active}" do - link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do + link_to request.path, class: 'btn has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) end end From b3fd0ca04d498504e93894378be98dc1bda7e259 Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Mon, 23 Feb 2015 14:27:52 -0800 Subject: [PATCH 032/109] Toggle sidebar button more obvious The toggle is now at the top of the sidebar because it is not noticeable near the bottom. By placing it at the top, users will immediately know that they can have more space if they desire versus on the bottom, they will have to search for it and that's not desired. Fixes #2044 --- .../stylesheets/sections/nav_sidebar.scss | 18 +++++++++++++----- app/views/layouts/_collapse_button.html.haml | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/sections/nav_sidebar.scss b/app/assets/stylesheets/sections/nav_sidebar.scss index 17923ca499..8841068b6a 100644 --- a/app/assets/stylesheets/sections/nav_sidebar.scss +++ b/app/assets/stylesheets/sections/nav_sidebar.scss @@ -108,7 +108,7 @@ width: $sidebar_width; .nav-sidebar { - margin-top: 20px; + margin-top: 29px; position: fixed; top: 45px; width: $sidebar_width; @@ -127,7 +127,7 @@ width: 52px; .nav-sidebar { - margin-top: 20px; + margin-top: 29px; position: fixed; top: 45px; width: 52px; @@ -144,14 +144,22 @@ } } } + + .collapse-nav a { + left: 0px; + padding: 5px 23px 3px 22px; + } } } .collapse-nav a { position: fixed; - bottom: 15px; - padding: 10px; - background: #DDD; + top: 47px; + padding: 5px 13px 3px 13px; + left: 197px; + background: #EEE; + color: black; + border: 1px solid rgba(0,0,0,0.035); } @media (max-width: $screen-md-max) { diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml index b3b338b55b..2ed51d87ca 100644 --- a/app/views/layouts/_collapse_button.html.haml +++ b/app/views/layouts/_collapse_button.html.haml @@ -1,4 +1,4 @@ - if nav_menu_collapsed? - = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse' + = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close" - else - = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse' + = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close" From 746dd89ab010299f731c195082087d32f25698df Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Mon, 23 Feb 2015 16:19:03 -0800 Subject: [PATCH 033/109] Fix 404 when deleting a project The deletion from the admin section was redirecting to the wrong address. --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 38341b1c8c..d1583e6ebf 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -102,7 +102,7 @@ class ProjectsController < ApplicationController flash[:alert] = 'Project deleted.' if request.referer.include?('/admin') - redirect_to admin_namespace_projects_path + redirect_to admin_namespaces_projects_path else redirect_to projects_dashboard_path end From f6add983d332758cd1ae22732da762cff608af1e Mon Sep 17 00:00:00 2001 From: Marcin Kulik Date: Tue, 24 Feb 2015 09:42:07 +0000 Subject: [PATCH 034/109] Use proper Gitorious icons on import pages --- app/assets/images/gitorious-logo-black.png | Bin 0 -> 809 bytes app/assets/images/gitorious-logo-blue.png | Bin 0 -> 495 bytes app/assets/stylesheets/sections/import.scss | 18 ++++++++++++++++++ app/views/import/gitorious/status.html.haml | 2 +- app/views/projects/new.html.haml | 2 +- 5 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 app/assets/images/gitorious-logo-black.png create mode 100644 app/assets/images/gitorious-logo-blue.png create mode 100644 app/assets/stylesheets/sections/import.scss diff --git a/app/assets/images/gitorious-logo-black.png b/app/assets/images/gitorious-logo-black.png new file mode 100644 index 0000000000000000000000000000000000000000..78f17a9af79dc2395acd9bcc2a391589dd6b6ed8 GIT binary patch literal 809 zcmV+^1J?YBP)G*X{kliLPwpCH_Ux^-m~D{fnn~wbKd#g`|kapbKi6DEOLf+ zN&$y=oMu=FqBx%JuhX%pIz@#hg8*wUQgI3eX`+i+7xFy6Z;2lf|+O3eT=?{Zlr#Q=P_NZ+T60H7bTbD9FnDV;VH zA`f-gCtb7}g3r*Sy@opADbZ*TJ;hVP&=#Ra2}uMH0sJEX7DqETm;o1l*~3KvX!H+o zQ0i^HOB?%bV!@DH<~e@foyBO$F^fs>*LGt_2#fNb_bvDo%C3kzo=3CYJPiS$A6~ShAh|z})|$Rug%3K+rg~Bm!|iNR1|hf}VBDE?U{j nDy!$>sq%+$UhqH%QaR#Z&H$a*+H(Ht00000NkvXXu0mjfir-pq literal 0 HcmV?d00001 diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..4962cffba3153927ddd8cd5ff295fe4ca142bfa8 GIT binary patch literal 495 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Y)RhkE)4%caKYZ?lYt_f1s;*b z3=De8Ak0{?)V>TT$X?><>&kwgNu1AE_|Z?p9}EnPHJ&bxAr-gY&e|w=$biT7sZ8;T z6d|?~LR)eirt)S)HZdA`dg%&j74B4Skjc^Z`dKOmc^xlq#JR8X;C&kdvr$f8mXRJ2aS*_MNDR*_yoTOyvF(6<;Njo*gv=n{#vX#_O67_!?$*J9p?F z<+tp;ShYZ<FVdQ I&MBb@0GSiJ)c^nh literal 0 HcmV?d00001 diff --git a/app/assets/stylesheets/sections/import.scss b/app/assets/stylesheets/sections/import.scss new file mode 100644 index 0000000000..3df4bb84bd --- /dev/null +++ b/app/assets/stylesheets/sections/import.scss @@ -0,0 +1,18 @@ +i.icon-gitorious { + display: inline-block; + background-position: 0px 0px; + background-size: contain; + background-repeat: no-repeat; +} + +i.icon-gitorious-small { + background-image: image-url('gitorious-logo-blue.png'); + width: 13px; + height: 13px; +} + +i.icon-gitorious-big { + background-image: image-url('gitorious-logo-black.png'); + width: 18px; + height: 18px; +} diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index 35ed0a717d..8ede5c3e84 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -1,5 +1,5 @@ %h3.page-title - %i.fa.fa-gitorious + %i.icon-gitorious.icon-gitorious-big Import repositories from Gitorious.org %p.light diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 33162ded4a..c37ae8d31d 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -70,7 +70,7 @@ .col-sm-2 .col-sm-10 = link_to new_import_gitorious_path do - %i.fa.fa-heart + %i.icon-gitorious.icon-gitorious-small Import projects from Gitorious.org %hr.prepend-botton-10 From 71a844cdaee129d4e300c20cbb27db009cf81b73 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Sat, 14 Feb 2015 18:18:05 +0200 Subject: [PATCH 035/109] Web Editor: save to new branch --- app/controllers/projects/blob_controller.rb | 26 ++++++++++++++++--- app/services/files/create_service.rb | 3 ++- app/services/files/update_service.rb | 3 ++- app/views/projects/blob/edit.html.haml | 7 +++++ app/views/projects/blob/new.html.haml | 7 +++++ .../satellite/files/edit_file_action.rb | 6 +++-- lib/gitlab/satellite/files/new_file_action.rb | 10 +++++-- 7 files changed, 53 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 1207548eae..4b7eb4df29 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -1,6 +1,7 @@ # Controller for viewing a file's blame class Projects::BlobController < Projects::ApplicationController include ExtractsPath + include ActionView::Helpers::SanitizeHelper # Raised when given an invalid file path class InvalidPathError < StandardError; end @@ -21,11 +22,18 @@ class Projects::BlobController < Projects::ApplicationController def create file_path = File.join(@path, File.basename(params[:file_name])) - result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute + result = Files::CreateService.new( + @project, + current_user, + params.merge(new_branch: sanitized_new_branch_name), + @ref, + file_path + ).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@ref, file_path)) + ref = sanitized_new_branch_name.presence || @ref + redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path)) else flash[:alert] = result[:message] render :new @@ -41,7 +49,13 @@ class Projects::BlobController < Projects::ApplicationController def update result = Files::UpdateService. - new(@project, current_user, params, @ref, @path).execute + new( + @project, + current_user, + params.merge(new_branch: sanitized_new_branch_name), + @ref, + @path + ).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" @@ -131,6 +145,8 @@ class Projects::BlobController < Projects::ApplicationController if from_merge_request diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + "#file-path-#{hexdigest(@path)}" + elsif sanitized_new_branch_name.present? + namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path)) else namespace_project_blob_path(@project.namespace, @project, @id) end @@ -140,4 +156,8 @@ class Projects::BlobController < Projects::ApplicationController # If blob edit was initiated from merge request page @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) end + + def sanitized_new_branch_name + @new_branch ||= sanitize(strip_tags(params[:new_branch])) + end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 2c457ef2ce..de5322e990 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -38,7 +38,8 @@ module Files created_successfully = new_file_action.commit!( params[:content], params[:commit_message], - params[:encoding] + params[:encoding], + params[:new_branch] ) if created_successfully diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index bcf0e7f3ce..328cf3a4b0 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -23,7 +23,8 @@ module Files edit_file_action.commit!( params[:content], params[:commit_message], - params[:encoding] + params[:encoding], + params[:new_branch] ) success diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 6884ad1f2f..1f61a0b940 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -14,6 +14,13 @@ = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}" + + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @ref, class: "form-control" + = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 45865d552a..d78a01f642 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -4,6 +4,13 @@ = render 'projects/blob/editor', ref: @ref = render 'shared/commit_message_container', params: params, placeholder: 'Add new file' + + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @ref, class: "form-control" + = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb index 82d71ab990..3cb9c0b5ec 100644 --- a/lib/gitlab/satellite/files/edit_file_action.rb +++ b/lib/gitlab/satellite/files/edit_file_action.rb @@ -10,7 +10,7 @@ module Gitlab # Returns false if committing the change fails # Returns false if pushing from the satellite to bare repo failed or was rejected # Returns true otherwise - def commit!(content, commit_message, encoding) + def commit!(content, commit_message, encoding, new_branch = nil) in_locked_and_timed_satellite do |repo| prepare_satellite!(repo) @@ -42,10 +42,12 @@ module Gitlab end + target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref + # push commit back to bare repo # will raise CommandFailed when push fails begin - repo.git.push({ raise: true, timeout: true }, :origin, ref) + repo.git.push({ raise: true, timeout: true }, :origin, target_branch) rescue Grit::Git::CommandFailed => ex log_and_raise(PushFailed, ex.message) end diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb index 69f7ffa94e..724dfa0d04 100644 --- a/lib/gitlab/satellite/files/new_file_action.rb +++ b/lib/gitlab/satellite/files/new_file_action.rb @@ -9,7 +9,7 @@ module Gitlab # Returns false if committing the change fails # Returns false if pushing from the satellite to bare repo failed or was rejected # Returns true otherwise - def commit!(content, commit_message, encoding) + def commit!(content, commit_message, encoding, new_branch = nil) in_locked_and_timed_satellite do |repo| prepare_satellite!(repo) @@ -45,9 +45,15 @@ module Gitlab # will raise CommandFailed when commit fails repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + target_branch = if new_branch.present? && !@project.empty_repo? + "#{ref}:#{new_branch}" + else + "#{current_ref}:#{ref}" + end + # push commit back to bare repo # will raise CommandFailed when push fails - repo.git.push({ raise: true, timeout: true }, :origin, "#{current_ref}:#{ref}") + repo.git.push({ raise: true, timeout: true }, :origin, target_branch) # everything worked true From 3177693c6f47fda14f84abf0b8f694aec3e076fc Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 23 Feb 2015 21:00:30 +0200 Subject: [PATCH 036/109] WebEditor: save to new branch: spinach --- features/project/source/browse_files.feature | 22 +++++++++++++++++++ features/steps/project/source/browse_files.rb | 15 ++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index ee8d0bffa9..90b966dd64 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -34,6 +34,17 @@ Feature: Project Source Browse Files Then I am redirected to the new file And I should see its new content + @javascript + Scenario: I can create and commit file and specify new branch + Given I click on "new file" link in repo + And I edit code + And I fill the new file name + And I fill the commit message + And I fill the new branch name + And I click on "Commit Changes" + Then I am redirected to the new file on new branch + And I should see its new content + @javascript @tricky Scenario: I can create file in empty repo Given I own an empty project @@ -83,6 +94,17 @@ Feature: Project Source Browse Files Then I am redirected to the ".gitignore" And I should see its new content + @javascript + Scenario: I can edit and commit file to new branch + Given I click on ".gitignore" file in repo + And I click button "Edit" + And I edit code + And I fill the commit message + And I fill the new branch name + And I click on "Commit Changes" + Then I am redirected to the ".gitignore" on new branch + And I should see its new content + @javascript @wip Scenario: If I don't change the content of the file I see an error message Given I click on ".gitignore" file in repo diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 98d8a60e1a..557555aee5 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -69,6 +69,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps fill_in :file_name, with: new_file_name end + step 'I fill the new branch name' do + fill_in :new_branch, with: 'new_branch_name' + end + step 'I fill the new file name with an illegal name' do fill_in :file_name, with: '.git' end @@ -148,6 +152,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore')) end + step 'I am redirected to the ".gitignore" on new branch' do + expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/.gitignore')) + end + step 'I am redirected to the permalink URL' do expect(current_path).to( eq(namespace_project_blob_path(@project.namespace, @project, @@ -161,6 +169,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps @project.namespace, @project, 'master/' + new_file_name)) end + step 'I am redirected to the new file on new branch' do + expect(current_path).to eq(namespace_project_blob_path( + @project.namespace, @project, 'new_branch_name/' + new_file_name)) + end + step "I don't see the permalink link" do expect(page).not_to have_link('permalink') end @@ -177,7 +190,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps click_link 'add a file' # Remove pre-receive hook so we can push without auth - FileUtils.rm(File.join(@project.repository.path, 'hooks', 'pre-receive')) + FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive')) end private From 51b18fe6f6558f14e5a8123d27f65f3557134822 Mon Sep 17 00:00:00 2001 From: Igor Bogoslavskyi Date: Tue, 24 Feb 2015 10:50:18 +0100 Subject: [PATCH 037/109] typo fixed fixed a typo in an url to gitlab-ssl. Now it does not return the 404 error --- doc/update/6.x-or-7.x-to-7.8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/6.x-or-7.x-to-7.8.md b/doc/update/6.x-or-7.x-to-7.8.md index 5884312c47..673d9253d6 100644 --- a/doc/update/6.x-or-7.x-to-7.8.md +++ b/doc/update/6.x-or-7.x-to-7.8.md @@ -179,7 +179,7 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab ### Change Nginx settings * HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stablef/lib/support/nginx/gitlab-ssl but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stable/lib/support/nginx/gitlab-ssl but with your settings. * A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section. ## 9. Start application From 63c4f3ca7665d076ea143991823ec99c8f996a73 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 24 Feb 2015 13:20:04 +0200 Subject: [PATCH 038/109] update gitlab-grack to 2.0.0.rc2 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 093f7bacc0..00d8f4429f 100644 --- a/Gemfile +++ b/Gemfile @@ -41,7 +41,7 @@ gem "browser" gem "gitlab_git", '7.0.0.rc14' # Ruby/Rack Git Smart-HTTP Server Handler -gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' +gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack' # LDAP Auth gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap" diff --git a/Gemfile.lock b/Gemfile.lock index 4bc47836e7..07cdbc2d75 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -183,7 +183,7 @@ GEM gitlab-flowdock-git-hook (0.4.2.2) gitlab-grit (>= 2.4.1) multi_json - gitlab-grack (2.0.0.pre) + gitlab-grack (2.0.0.rc2) rack (~> 1.5.1) gitlab-grit (2.7.2) charlock_holmes (~> 0.6) @@ -668,7 +668,7 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) github-markup gitlab-flowdock-git-hook (~> 0.4.2) - gitlab-grack (~> 2.0.0.pre) + gitlab-grack (~> 2.0.0.rc2) gitlab-linguist (~> 3.0.0) gitlab_emoji (~> 0.0.1.1) gitlab_git (= 7.0.0.rc14) From 9338c6325263d950966e87ddb23095075f18558e Mon Sep 17 00:00:00 2001 From: kfei Date: Wed, 17 Dec 2014 00:53:17 -0800 Subject: [PATCH 039/109] Gracefully shutdown services in Docker container The problem is `docker stop` only sends SIGTERM to the PID 1 inside the container, and the PID 1 (`/bin/sh -c ...`) does not take care of signals. Hence the services (e.g., postgresql, redis, sidekiq, etc) never have chances to graceful shutdown. Docker just kills the container after its 10 seconds timeout by default. What this commit does: 1) Add a wrapper as the default executable of Docker container. Which starts services through `runit`, reconfigure Gitlab by `gitlab-ctl` and gracefully shutdown all services when a SIGTERM is received. 2) Create an `assets` directory for assets. 3) Add `.dockerignore` file. Now you'll see the following log messages after `docker stop`: ``` SIGTERM signal received, try to gracefully shutdown all services... ok: down: logrotate: 1s, normally up ok: down: nginx: 0s, normally up ok: down: postgresql: 1s, normally up ok: down: redis: 0s, normally up ok: down: sidekiq: 0s, normally up ok: down: unicorn: 0s, normally up ``` Signed-off-by: kfei --- docker/.dockerignore | 1 + docker/Dockerfile | 11 +++++++---- docker/{ => assets}/gitlab.rb | 0 docker/assets/wrapper | 17 +++++++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 docker/.dockerignore rename docker/{ => assets}/gitlab.rb (100%) create mode 100755 docker/assets/wrapper diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000000..dd449725e1 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1 @@ +*.md diff --git a/docker/Dockerfile b/docker/Dockerfile index cfb89357a6..3a0a55e18e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -26,9 +26,12 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \ # Expose web & ssh EXPOSE 80 22 -# Volume & configuration +# Declare volumes VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"] -ADD gitlab.rb /etc/gitlab/ -# Default is to run runit & reconfigure -CMD gitlab-ctl reconfigure & /opt/gitlab/embedded/bin/runsvdir-start \ No newline at end of file +# Copy assets +COPY assets/gitlab.rb /etc/gitlab/ +COPY assets/wrapper /usr/local/bin/ + +# Wrapper to handle signal, trigger runit and reconfigure GitLab +CMD ["/usr/local/bin/wrapper"] diff --git a/docker/gitlab.rb b/docker/assets/gitlab.rb similarity index 100% rename from docker/gitlab.rb rename to docker/assets/gitlab.rb diff --git a/docker/assets/wrapper b/docker/assets/wrapper new file mode 100755 index 0000000000..9e6e7a0590 --- /dev/null +++ b/docker/assets/wrapper @@ -0,0 +1,17 @@ +#!/bin/bash + +function sigterm_handler() { + echo "SIGTERM signal received, try to gracefully shutdown all services..." + gitlab-ctl stop +} + +trap "sigterm_handler; exit" TERM + +function entrypoint() { + # Default is to run runit and reconfigure GitLab + gitlab-ctl reconfigure & + /opt/gitlab/embedded/bin/runsvdir-start & + wait +} + +entrypoint From 1e1a95f5c36357b03df9420903d421edf7ac08c4 Mon Sep 17 00:00:00 2001 From: Bugagazavr Date: Tue, 24 Feb 2015 15:12:25 +0300 Subject: [PATCH 040/109] Update Changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d879ee8572..deda7ffc32 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 7.9.0 (unreleased) - Improve error messages for file edit failures - Improve UI for commits, issues and merge request lists - Fix commit comments on first line of diff not rendering in Merge Request Discussion view. + - Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev) v 7.8.0 - Fix access control and protection against XSS for note attachments and other uploads. From ad6d6232342558705c54ba70a94f9d7ddbd00f8c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Feb 2015 16:59:50 +0100 Subject: [PATCH 041/109] Add Bitbucket importer. --- CHANGELOG | 2 + Gemfile | 1 + Gemfile.lock | 5 + .../images/authbuttons/bitbucket_32.png | Bin 0 -> 2713 bytes .../images/authbuttons/bitbucket_64.png | Bin 0 -> 2163 bytes .../import/bitbucket_controller.rb | 74 +++++++++++++++ app/helpers/oauth_helper.rb | 4 +- app/helpers/projects_helper.rb | 4 + app/models/project.rb | 2 +- app/models/user.rb | 1 + app/views/import/base/create.js.haml | 7 ++ app/views/import/bitbucket/status.html.haml | 44 +++++++++ app/views/import/github/status.html.haml | 2 +- app/views/import/gitlab/status.html.haml | 2 +- .../_bitbucket_import_modal.html.haml | 9 ++ app/views/projects/new.html.haml | 13 +++ app/workers/repository_import_worker.rb | 2 + config/gitlab.yml.example | 18 ++-- config/routes.rb | 5 + ...tbucket_access_token_and_secret_to_user.rb | 6 ++ db/schema.rb | 40 ++++---- doc/integration/bitbucket.m | 1 + lib/gitlab/bitbucket_import/client.rb | 88 ++++++++++++++++++ lib/gitlab/bitbucket_import/importer.rb | 52 +++++++++++ lib/gitlab/bitbucket_import/key_adder.rb | 22 +++++ .../bitbucket_import/project_creator.rb | 39 ++++++++ lib/gitlab/github_import/client.rb | 6 +- lib/gitlab/github_import/importer.rb | 2 +- lib/gitlab/gitlab_import/client.rb | 10 +- lib/gitlab/gitlab_import/importer.rb | 2 +- lib/gitlab/import_formatter.rb | 2 +- .../import/bitbucket_controller_spec.rb | 77 +++++++++++++++ .../bitbucket_import/project_creator_spec.rb | 22 +++++ .../project_creator_spec.rb} | 7 +- ...ect_creator.rb => project_creator_spec.rb} | 5 +- 35 files changed, 522 insertions(+), 54 deletions(-) create mode 100644 app/assets/images/authbuttons/bitbucket_32.png create mode 100644 app/assets/images/authbuttons/bitbucket_64.png create mode 100644 app/controllers/import/bitbucket_controller.rb create mode 100644 app/views/import/bitbucket/status.html.haml create mode 100644 app/views/projects/_bitbucket_import_modal.html.haml create mode 100644 db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb create mode 100644 doc/integration/bitbucket.m create mode 100644 lib/gitlab/bitbucket_import/client.rb create mode 100644 lib/gitlab/bitbucket_import/importer.rb create mode 100644 lib/gitlab/bitbucket_import/key_adder.rb create mode 100644 lib/gitlab/bitbucket_import/project_creator.rb create mode 100644 spec/controllers/import/bitbucket_controller_spec.rb create mode 100644 spec/lib/gitlab/bitbucket_import/project_creator_spec.rb rename spec/lib/gitlab/{github/project_creator.rb => github_import/project_creator_spec.rb} (77%) rename spec/lib/gitlab/gitlab_import/{project_creator.rb => project_creator_spec.rb} (91%) diff --git a/CHANGELOG b/CHANGELOG index d879ee8572..5a3a2ca2e2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -72,6 +72,8 @@ v 7.8.0 - Improve database performance for GitLab - Add Asana service (Jeremy Benoist) - Improve project web hooks with extra data + - Add Bitbucket omniauth provider. + - Add Bitbucket importer. v 7.7.2 - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch diff --git a/Gemfile b/Gemfile index 093f7bacc0..ad01b2b43e 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem 'omniauth-github' gem 'omniauth-shibboleth' gem 'omniauth-kerberos' gem 'omniauth-gitlab' +gem 'omniauth-bitbucket' gem 'doorkeeper', '2.1.0' gem "rack-oauth2", "~> 1.0.5" diff --git a/Gemfile.lock b/Gemfile.lock index 4bc47836e7..226591a50a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -338,6 +338,10 @@ GEM omniauth (1.1.4) hashie (>= 1.2, < 3) rack + omniauth-bitbucket (0.0.2) + multi_json (~> 1.7) + omniauth (~> 1.1) + omniauth-oauth (~> 1.0) omniauth-github (1.1.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) @@ -701,6 +705,7 @@ DEPENDENCIES nprogress-rails octokit (= 3.7.0) omniauth (~> 1.1.3) + omniauth-bitbucket omniauth-github omniauth-gitlab omniauth-google-oauth2 diff --git a/app/assets/images/authbuttons/bitbucket_32.png b/app/assets/images/authbuttons/bitbucket_32.png new file mode 100644 index 0000000000000000000000000000000000000000..27702eb973d1c3c0b9a39eee082ca72d38339cc2 GIT binary patch literal 2713 zcmZ`*3p~^78y~tb)c!~k#|$NxjV;v7!WfgG5i4^UCKKD*9CHv4hY%>T%6zJQtvd&<}w?H)3 zN>;-BU|cc-W(YNa{Yb>7`hQ>E|ENTkpUNm)>Q7`M%U9WQF}|)5C9EhCPxqtxhgy-@ z6g1qx&`93^u5W1KVu(N*z>&LmqhM<|-!oiRW0({&o6dBh(}U1XR4yebSTqGApm3<+ zSM6%XDzPXJq$86`5f)ffMzoOrf7n_+h91mdQdlez-)M!u8e7XJP+0UJj>!E%8kPMm z#2R?5>d<#P+62B5W;M80K>U$lHMmM3+6d%wrHEG4UC7Pe zQbfrtOIAT^*~?)L{VdEX!-^0E6P@K%XZ!2LSw@972MrYN=)boR4V?6NKMeqEIEAya za3M;iyHSH&tW}m;!fReeSH{}}mI6~jcys)iB0=5BGF}sl`3n+YZ;O$J@IZ?nes^n& zVMxiPE5+>C5hK?=kso^kSgz2{+>*@LD!t`J?5}4`hUotDW^;O+)GEYu?$U29S8D?< z&^S^XE;#2|fhsd3Vs*^%8y;{|c`}5CvW+J=>n%AJr!;a*o!-?%&EW@RNc8OHb%h>o zF-;RBIoOUa75KSzwkaCpU9?ze^Y-rg-dD zdg^!*2o}?-_JX99D2`I3ROIS!vhg)dzustZps>=aIeNjk7FO-#(sOdosaH#d^M@i3 z%(x?7xusy-$fcl_oAgLhtDb*Wt+A%^eTUrftQ1d_8z?}sF(-y`rY5kiS#3V_Wht~Z zW^^0L$cr;%)yi4Wj*aWNE2sO5SG3vem(qeBLtqA`$=1i=pDd!g*M;h$Ka!2Z1{L&P z$u@NZf_5eq0f1l&S{OV&mPl?C(mI7m8cL$%eTDg+^U9PnzA3Ov%|6d%0Te zS`g9Ebc(KeWu~qhn80iD`wLe%tnqNS4AuJ37Q^mnLuQ@?+4*KJ3&7V?|8P_DC_b$< z)l}<&HKw6rB+^z7o3{z8gEn~)+z)CXDQ-szq7~_J4>^Lc6gxr8-#mEC)9O*nUX|p# zwgm!cSI^}R}dZwyi z#iBa|exJX#W= zLLDN=Kk`Mj1-!TVKv|et&L`n#Zn}Gx#C>?x3=Az0H(Q^Gwah`NHwnB5Q#a~Huv|`u zo=y!vPMb(PGn9>V>bS{&iFvSV|CYhxUF(vNBkylbFlzz&mjxaW{;Ai+_+#<+x*Nm0 zb95wMS+D2c2;irTLG8T`B+0*Y=qPQRQcQgt+~X+~*gpo;Ucw(fTO-F7x9ESfSzpn4 zuVdVBXC6#9c9(YL`OSHFsKk$JY9IL3bz!MWY=XoF^(CCcN0_I=@Gf_-u4~C1jW(~8ArF3J!^MM7`oRR z?RXrp&BGCP9J5u=V*YK!vCxHVy|T=)zK%DSPWwo=ayN5vqvzYA%8pCl&i(Tw;%!4Y zO(RNHZLr(*T#FZ%-HE@(ypyuW`WCf9{N2@)xgTc(5OTQjLU<8u-9Gb#A z4a$S!Qja;_`E37OgJ@^{=J0>gyas2C&zVv+s>GpoEssJ9AMEsl3-Xw03~Y)D&Oe~Z zLAt9`tN}|=9jv$`-&j?CQx7rH3v?-#X`iBw_AZpEbAPke0*>q0K3in!#2R!oI4ck` zytT2Px~ex*`oQCIL1<6NuUj4HxU;>92OmdrO&n++RkFo9@oJ^y1-r-YV?7@a{1Q+f zDSMkuzWLUw)J*Auw;Ks5i4)@nR46UA2 zmwM&%krW?sUNxhQeWFV(Zm8x&$|(57B*?obtFGUmCSPH|eB1aZ-I-RD%%KGDB5+zJ z@3l-Df*N8T1$_f&iZy>$j_z@Upo;^dL5P;d$2s(SrrMKzma{Ei%zdX$N7R5c?nwXWa;9p*_m)j*51XX05;U$nbepp!G-TG;r^8FVk9I3cu(*80QB)VcObzvy~ zft2JC1X3qrqa`mXGxAE6qZ1gHe-wRu(deHgt{}OJ`kdWr-RvBqd?~f>w(eZo-=6l$ gqZTZLJY{B5E#UAGZ=PVPRP?`yvv#m5vGk7mFT{djfB*mh literal 0 HcmV?d00001 diff --git a/app/assets/images/authbuttons/bitbucket_64.png b/app/assets/images/authbuttons/bitbucket_64.png new file mode 100644 index 0000000000000000000000000000000000000000..4b90a57bc7de93163275f99ea2971e2740af98af GIT binary patch literal 2163 zcmV-(2#oiMP)2uhW|vhI6clA;HOc~sCx{2egDXMtKom7mKv^T8ct;fPlX#%Ui}gx8f}-Mq z8i53&iTVRjz=KFcSXNM@D}srF8g{ySd}H3pR^Ig1Oi%ahB0rw&W_G&jtEa!JuIj3F z^7%Yyqwm?bInLn?j(6--#~D1u#044R2)luLa0a**G=ha-71#u}0UzW`Amn$q@!C}^ zYc{xsb@XMOj*ua){w;gl<&IN7-g#rmivQY~^Mn6>mLkB7X!IUn2)G{11FIQuzJvIa zb-uwEhJdaSGTbBrwBcRBncxMm-V$5@a_!(71cG0{a(!T_cO7^RoCbDR5pe~0Mh4V@ zyI4U~s%itn3f}pU^%SdfEw}^J8X4e~D!^j~RN=xIV6)s^pFcxOgSS)Ir(iyK7R&@U zf@xqfm;lCui@+sd3b+c~0%n1izyh!gYy>+ZvyCxGYI5QLI4}i z!iiM)3K(9bu^z}qkP)(ij^;{tj!EySgorMahk!K@(2D}RNCE5uFG4^b0$MHoFaVe7 zsA~l70hc7s0OA>Sc=Da;Ahcv@D+J^r;AwUN&o%}$LqGrlL3;wo$cVd~u{1nQ5t<>V zNERpo1OyQ9GX!*J7jWAXu$~!U6Hs^--wgT`NqtRs&xUpjx#P;doCjT&b2WoT{#MvMFE*80a*%|Dgt65laicFd&5$%C%(@3tYs9>fCdW4+A?4y1+b!u z5egNphXRJ$GQgvNdIO%xF90s;aG_!a`HD8MbY z7FbLH{w^TEr+`J)wE)RbKqCd@b^!r73V56XvNi!(3YgXb84wDU&Vb1xz?uQ0DmV!G z#Qu5ugWw1X$k+sU6mSRx{9_i70zpKB{~*531JZ0f*bV`8G6q-&!5RoyD}g6YL$a7l z>A|3m4CZGaim2l3c#`nWA1l(7SfD8pRBoL4T1En!H^Y6YT;A`ZXqk*45zyuKxs|yTa zr6hL$8x36cYnCTlZMw%tX~tM^wv-w7)!g7D0&2mQIQFHFKL8xrEqc+i0D4NE0uFdo z%8YXZeg*qcfE!1EO95H%wG^?3$+f{h94Uj@9_0{9p# z+<|%0r{V^2lccmm>cKd0E;yv{xpbM^<2GJmeU;^Kd%QdW_sGZ{w-5dWPBbKV#1TAF zX#6nnx4cd-l^n0RBMku;h}ClD11(^L>?Bu#YEmV~aYliyQbsu$FgA$*m+9C8d|xn| zU`?=tgW>^}#ZVvc5NIu@@e<>D(2Z?y%RMFL8Qag>l1d<(**C8S=YX;NeJ=P-v!_&~ zoBnBZJ1^NWITpYI*V#5D<7}oX3CqpN0jU!t_9OxT`zO0YBU4n*4~4Nr06iiXvkW~4 zw1d0Rdr8d61*lDM4Ac0pi2mo>grv0LswdK3VN4QhiURbF4H*D#3l(IC-CV^El=B%ZZR5Cy%EHMV5Ku&7k3kJ2*&5D@|U@co?6_fFw(WGy(AWqzjh5i_QTB<55nLX%`_t8aAj z!fy^Hoh=4n zkPLvSjsZPEOR>d@3K4>04Hd!|i>;r)Ua5}(#HH1xZE4u~CqyKcPvo$6DgtyMd|m}~ zA}V0EblG$SNTJhqVgXY+Lcqx~2J8%kzT(Ii9Kvd)ZB3oHV}T0(4N^hf~*P zThOszblPQZ;9c(_v2>^at++clAIxprM@Wn|dJgY+H;th)1laz&d~Yxu%m8nKFTs`= puf?};oj;Fd45Q%4{|-+g;9pZ(FBz?c<5BAccess denied! Please verify you can add deploy keys to this repository.

"") - else :plain job = $("tr#repo_#{@repo_id}") job.attr("id", "project_#{@project.id}") + target_field = job.find(".import-target") + target_field.empty() + target_field.append('#{link_to @project.path_with_namespace, @project}') $("table.import-jobs tbody").prepend(job) job.addClass("active").find(".import-actions").html(" started") diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml new file mode 100644 index 0000000000..7a2613e4b0 --- /dev/null +++ b/app/views/import/bitbucket/status.html.haml @@ -0,0 +1,44 @@ +%h3.page-title + %i.fa.fa-bitbucket + Import repositories from Bitbucket + +%p.light + Select projects you want to import. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From Bitbucket + %th To GitLab + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td= project.import_source + %td + %strong= link_to project.path_with_namespace, project + %td.job-status + - if project.import_status == 'finished' + %span.cgreen + %i.fa.fa-check + done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} + %td= "#{repo["owner"]}/#{repo["slug"]}" + %td.import-target + = "#{repo["owner"]}/#{repo["slug"]}" + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + $ -> + new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}") diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 84d9903fe1..b1538b1a41 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title %i.fa.fa-github - Import repositories from GitHub.com + Import repositories from GitHub %p.light Select projects you want to import. diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index d1e48dfad2..43db102994 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -1,5 +1,5 @@ %h3.page-title - %i.fa.fa-github + %i.fa.fa-heart Import repositories from GitLab.com %p.light diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml new file mode 100644 index 0000000000..dd7aacc7e6 --- /dev/null +++ b/app/views/projects/_bitbucket_import_modal.html.haml @@ -0,0 +1,9 @@ +%div#bitbucket_import_modal.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 GitHub OAuth import + .modal-body + You need to setup integration with Bitbucket first. + = link_to 'How to setup integration with Bitbucket', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md' \ No newline at end of file diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 5216f30811..875c092fd1 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -53,6 +53,19 @@ Import projects from GitHub = render 'github_import_modal' + .project-import.form-group + .col-sm-2 + .col-sm-10 + - if bitbucket_import_enabled? + = link_to status_import_bitbucket_path do + %i.fa.fa-bitbucket + Import projects from Bitbucket + - else + = link_to '#', class: 'how_to_import_link light' do + %i.fa.fa-bitbucket + Import projects from Bitbucket + = render 'bitbucket_import_modal' + - unless request.host == 'gitlab.com' .project-import.form-group .col-sm-2 diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 5f9970d379..d7e759fb47 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -14,6 +14,8 @@ class RepositoryImportWorker Gitlab::GithubImport::Importer.new(project).execute elsif project.import_type == 'gitlab' Gitlab::GitlabImport::Importer.new(project).execute + elsif project.import_type == 'bitbucket' + Gitlab::BitbucketImport::Importer.new(project).execute else true end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 044b1f66b2..6dff07cf9d 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -207,17 +207,19 @@ production: &base # arguments, followed by optional 'args' which can be either a hash or an array. # Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html providers: - # - { name: 'google_oauth2', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'google_oauth2', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # args: { access_type: 'offline', approval_prompt: '' } } - # - { name: 'twitter', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET'} - # - { name: 'github', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'twitter', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET'} + # - { name: 'github', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # args: { scope: 'user:email' } } - # - { name: 'gitlab', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'gitlab', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # args: { scope: 'api' } } + # - { name: 'bitbucket', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET'} diff --git a/config/routes.rb b/config/routes.rb index ecd439aece..57964bdc3b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -68,6 +68,11 @@ Gitlab::Application.routes.draw do get :jobs end + resource :bitbucket, only: [:create, :new], controller: :bitbucket do + get :status + get :callback + get :jobs + end resource :gitorious, only: [:create, :new], controller: :gitorious do get :status get :callback diff --git a/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb new file mode 100644 index 0000000000..23ac1b399e --- /dev/null +++ b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb @@ -0,0 +1,6 @@ +class AddBitbucketAccessTokenAndSecretToUser < ActiveRecord::Migration + def change + add_column :users, :bitbucket_access_token, :string + add_column :users, :bitbucket_access_token_secret, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index e11a068c9c..8069d95c69 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150213121042) do +ActiveRecord::Schema.define(version: 20150217123345) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -410,12 +410,12 @@ ActiveRecord::Schema.define(version: 20150213121042) do end create_table "users", force: true do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -423,22 +423,22 @@ ActiveRecord::Schema.define(version: 20150213121042) do t.datetime "created_at" t.datetime "updated_at" t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", default: 1, null: false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", default: 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" @@ -447,13 +447,15 @@ ActiveRecord::Schema.define(version: 20150213121042) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false t.string "github_access_token" t.string "gitlab_access_token" t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "bitbucket_access_token" + t.string "bitbucket_access_token_secret" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/doc/integration/bitbucket.m b/doc/integration/bitbucket.m new file mode 100644 index 0000000000..30404ce4c5 --- /dev/null +++ b/doc/integration/bitbucket.m @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb new file mode 100644 index 0000000000..3d2ef78ee7 --- /dev/null +++ b/lib/gitlab/bitbucket_import/client.rb @@ -0,0 +1,88 @@ +module Gitlab + module BitbucketImport + class Client + attr_reader :consumer, :api + + def initialize(access_token = nil, access_token_secret = nil) + @consumer = ::OAuth::Consumer.new( + config.app_id, + config.app_secret, + bitbucket_options + ) + + if access_token && access_token_secret + @api = ::OAuth::AccessToken.new(@consumer, access_token, access_token_secret) + end + end + + def request_token(redirect_uri) + request_token = consumer.get_request_token(oauth_callback: redirect_uri) + + { + oauth_token: request_token.token, + oauth_token_secret: request_token.secret, + oauth_callback_confirmed: request_token.callback_confirmed?.to_s + } + end + + def authorize_url(request_token, redirect_uri) + request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash) + + if request_token.callback_confirmed? + request_token.authorize_url + else + request_token.authorize_url(oauth_callback: redirect_uri) + end + end + + def get_token(request_token, oauth_verifier, redirect_uri) + request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash) + + if request_token.callback_confirmed? + request_token.get_access_token(oauth_verifier: oauth_verifier) + else + request_token.get_access_token(oauth_callback: redirect_uri) + end + end + + def user + JSON.parse(api.get("/api/1.0/user").body) + end + + def issues(project_identifier) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues").body) + end + + def issue_comments(project_identifier, issue_id) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body) + end + + def project(project_identifier) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}").body) + end + + def deploy_key(project_identifier) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find { |key| key["label"] =~ /GitLab/ } + end + + def add_deploy_key(project_identifier, key) + JSON.parse(api.post("/api/1.0/repositories/#{project_identifier}/deploy-keys", key: key, label: "GitLab import key").body) + end + + def projects + JSON.parse(api.get("/api/1.0/user/repositories").body). + select { |repo| repo["scm"] == "git" } + end + + private + + def config + Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"} + end + + def bitbucket_options + OmniAuth::Strategies::Bitbucket.default_options[:client_options] + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb new file mode 100644 index 0000000000..42c93707ca --- /dev/null +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -0,0 +1,52 @@ +module Gitlab + module BitbucketImport + class Importer + attr_reader :project, :client + + def initialize(project) + @project = project + @client = Client.new(project.creator.bitbucket_access_token, project.creator.bitbucket_access_token_secret) + @formatter = Gitlab::ImportFormatter.new + end + + def execute + project_identifier = project.import_source + + return true unless client.project(project_identifier)["has_issues"] + + #Issues && Comments + issues = client.issues(project_identifier) + + issues["issues"].each do |issue| + body = @formatter.author_line(issue["reported_by"]["username"], issue["content"]) + + comments = client.issue_comments(project_identifier, issue["local_id"]) + + if comments.any? + body += @formatter.comments_header + end + + comments.each do |comment| + body += @formatter.comment(comment["author_info"]["username"], comment["utc_created_on"], comment["content"]) + end + + project.issues.create!( + description: body, + title: issue["title"], + state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened', + author_id: gl_user_id(project, issue["reported_by"]["username"]) + ) + end + + true + end + + private + + def gl_user_id(project, bitbucket_id) + user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s) + (user && user.id) || project.creator_id + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/key_adder.rb b/lib/gitlab/bitbucket_import/key_adder.rb new file mode 100644 index 0000000000..207811237b --- /dev/null +++ b/lib/gitlab/bitbucket_import/key_adder.rb @@ -0,0 +1,22 @@ +module Gitlab + module BitbucketImport + class KeyAdder + attr_reader :repo, :current_user, :client + + def initialize(repo, current_user) + @repo, @current_user = repo, current_user + @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + end + + def execute + project_identifier = "#{repo["owner"]}/#{repo["slug"]}" + return true if client.deploy_key(project_identifier) + + # TODO: Point to actual public key. + client.add_deploy_key(project_identifier, File.read("/Users/douwemaan/.ssh/id_rsa.pub")) + + true + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb new file mode 100644 index 0000000000..db33af2c2d --- /dev/null +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -0,0 +1,39 @@ +module Gitlab + module BitbucketImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + @project = Project.new( + name: repo["name"], + path: repo["slug"], + description: repo["description"], + namespace: namespace, + creator: current_user, + visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, + import_type: "bitbucket", + import_source: "#{repo["owner"]}/#{repo["slug"]}", + import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git" + ) + + if @project.save! + @project.reload + + if @project.import_failed? + @project.import_retry + else + @project.import_start + end + end + + @project + end + end + end +end diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index c9904fe877..676d226bdd 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -46,11 +46,7 @@ module Gitlab end def github_options - { - site: 'https://api.github.com', - authorize_url: 'https://github.com/login/oauth/authorize', - token_url: 'https://github.com/login/oauth/access_token' - } + OmniAuth::Strategies::GitHub.default_options[:client_options] end end end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index bc2b645b2d..23832b3233 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -20,7 +20,7 @@ module Gitlab body += @formatter.comments_header client.issue_comments(project.import_source, issue.number).each do |c| - body += @formatter.comment_to_md(c.user.login, c.created_at, c.body) + body += @formatter.comment(c.user.login, c.created_at, c.body) end end diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb index 2206b68da9..ecf4ff94e3 100644 --- a/lib/gitlab/gitlab_import/client.rb +++ b/lib/gitlab/gitlab_import/client.rb @@ -9,7 +9,7 @@ module Gitlab @client = ::OAuth2::Client.new( config.app_id, config.app_secret, - github_options + gitlab_options ) if access_token @@ -70,12 +70,8 @@ module Gitlab Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"} end - def github_options - { - site: 'https://gitlab.com/', - authorize_url: 'oauth/authorize', - token_url: 'oauth/token' - } + def gitlab_options + OmniAuth::Strategies::GitLab.default_options[:client_options] end end end diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index 5f9b14399a..c5304a0699 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -25,7 +25,7 @@ module Gitlab end comments.each do |comment| - body += @formatter.comment_to_md(comment["author"]["name"], comment["created_at"], comment["body"]) + body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"]) end project.issues.create!( diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb index ebb4b87f7e..72e041a90b 100644 --- a/lib/gitlab/import_formatter.rb +++ b/lib/gitlab/import_formatter.rb @@ -1,6 +1,6 @@ module Gitlab class ImportFormatter - def comment_to_md(author, date, body) + def comment(author, date, body) "\n\n*By #{author} on #{date}*\n\n#{body}" end diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb new file mode 100644 index 0000000000..84e37ae560 --- /dev/null +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Import::BitbucketController do + let(:user) { create(:user, bitbucket_access_token: 'asd123', bitbucket_access_token_secret: "sekret") } + + before do + sign_in(user) + end + + describe "GET callback" do + before do + session[:oauth_request_token] = {} + end + + it "updates access token" do + token = "asdasd12345" + secret = "sekrettt" + access_token = double(token: token, secret: secret) + Gitlab::BitbucketImport::Client.any_instance.stub(:get_token).and_return(access_token) + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket") + + get :callback + + expect(user.reload.bitbucket_access_token).to eq(token) + expect(user.reload.bitbucket_access_token_secret).to eq(secret) + expect(controller).to redirect_to(status_import_bitbucket_url) + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(slug: 'vim', owner: 'asd') + end + + it "assigns variables" do + @project = create(:project, import_type: 'bitbucket', creator_id: user.id) + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end + + describe "POST create" do + before do + @repo = { + slug: 'vim', + owner: "john" + }.with_indifferent_access + end + + it "takes already existing namespace" do + namespace = create(:namespace, name: "john", owner: user) + expect(Gitlab::BitbucketImport::KeyAdder). + to receive(:new).with(@repo, user). + and_return(double(execute: true)) + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(@repo, namespace, user). + and_return(double(execute: true)) + controller.stub_chain(:client, :project).and_return(@repo) + + post :create, format: :js + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb new file mode 100644 index 0000000000..f552310584 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Gitlab::BitbucketImport::ProjectCreator do + let(:user) { create(:user, bitbucket_access_token: "asdffg", bitbucket_access_token_secret: "sekret") } + let(:repo) { { + name: 'Vim', + slug: 'vim', + is_private: true, + owner: "asd"}.with_indifferent_access + } + let(:namespace){ create(:namespace) } + + it 'creates project' do + allow_any_instance_of(Project).to receive(:add_import_job) + + project_creator = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute + + expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git") + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + end +end diff --git a/spec/lib/gitlab/github/project_creator.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb similarity index 77% rename from spec/lib/gitlab/github/project_creator.rb rename to spec/lib/gitlab/github_import/project_creator_spec.rb index 3686ddbf17..8d594a112d 100644 --- a/spec/lib/gitlab/github/project_creator.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Github::ProjectCreator do +describe Gitlab::GithubImport::ProjectCreator do let(:user) { create(:user, github_access_token: "asdffg") } let(:repo) { OpenStruct.new( login: 'vim', @@ -15,9 +15,8 @@ describe Gitlab::Github::ProjectCreator do it 'creates project' do allow_any_instance_of(Project).to receive(:add_import_job) - project_creator = Gitlab::Github::ProjectCreator.new(repo, namespace, user) - project_creator.execute - project = Project.last + project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) diff --git a/spec/lib/gitlab/gitlab_import/project_creator.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb similarity index 91% rename from spec/lib/gitlab/gitlab_import/project_creator.rb rename to spec/lib/gitlab/gitlab_import/project_creator_spec.rb index e5d917830b..4c0d64ed13 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::GitlabImport::ProjectCreator do let(:user) { create(:user, gitlab_access_token: "asdffg") } - let(:repo) {{ + let(:repo) { { name: 'vim', path: 'vim', visibility_level: Gitlab::VisibilityLevel::PRIVATE, @@ -16,8 +16,7 @@ describe Gitlab::GitlabImport::ProjectCreator do allow_any_instance_of(Project).to receive(:add_import_job) project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user) - project_creator.execute - project = Project.last + project = project_creator.execute expect(project.import_url).to eq("https://oauth2:asdffg@gitlab.com/asd/vim.git") expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) From 448817c4de965bf7286f33a3447937987a8864a1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Feb 2015 22:52:32 +0100 Subject: [PATCH 042/109] Load public key in initializer. --- app/controllers/application_controller.rb | 13 +++++++++++++ app/controllers/import/bitbucket_controller.rb | 5 +++++ app/controllers/import/github_controller.rb | 5 +++++ app/controllers/import/gitlab_controller.rb | 5 +++++ app/helpers/oauth_helper.rb | 2 ++ app/helpers/projects_helper.rb | 12 ------------ config/initializers/public_key.rb | 2 ++ lib/gitlab/bitbucket_import.rb | 6 ++++++ lib/gitlab/bitbucket_import/key_adder.rb | 9 ++++++--- 9 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 config/initializers/public_key.rb create mode 100644 lib/gitlab/bitbucket_import.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index eb3be08df5..7940b5cb3f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -16,6 +16,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :abilities, :can?, :current_application_settings + helper_method :github_import_enabled?, :gitlab_import_enabled?, :bitbucket_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -313,4 +314,16 @@ class ApplicationController < ActionController::Base set_filter_values(merge_requests) merge_requests end + + def github_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:github) + end + + def gitlab_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:gitlab) + end + + def bitbucket_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:bitbucket) && Gitlab::BitbucketImport.public_key.present? + end end diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 27e91f49f2..89de5c5205 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -1,4 +1,5 @@ class Import::BitbucketController < Import::BaseController + before_filter :verify_bitbucket_import_enabled before_filter :bitbucket_auth, except: :callback # rescue_from OAuth::Error, with: :bitbucket_unauthorized @@ -55,6 +56,10 @@ class Import::BitbucketController < Import::BaseController @client ||= Gitlab::BitbucketImport::Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) end + def verify_bitbucket_import_enabled + not_found! unless bitbucket_import_enabled? + end + def bitbucket_auth if current_user.bitbucket_access_token.blank? go_to_bitbucket_for_permissions diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index c869c7c86f..dc7668ee6f 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -1,4 +1,5 @@ class Import::GithubController < Import::BaseController + before_filter :verify_github_import_enabled before_filter :github_auth, except: :callback rescue_from Octokit::Unauthorized, with: :github_unauthorized @@ -44,6 +45,10 @@ class Import::GithubController < Import::BaseController @client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token) end + def verify_github_import_enabled + not_found! unless github_import_enabled? + end + def github_auth if current_user.github_access_token.blank? go_to_github_for_permissions diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index a51ea36aff..74f992b469 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -1,4 +1,5 @@ class Import::GitlabController < Import::BaseController + before_filter :verify_gitlab_import_enabled before_filter :gitlab_auth, except: :callback rescue_from OAuth2::Error, with: :gitlab_unauthorized @@ -41,6 +42,10 @@ class Import::GitlabController < Import::BaseController @client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token) end + def verify_gitlab_import_enabled + not_found! unless gitlab_import_enabled? + end + def gitlab_auth if current_user.gitlab_access_token.blank? go_to_gitlab_for_permissions diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index 848d74c18c..1a0ad17b60 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -20,4 +20,6 @@ module OauthHelper def additional_providers enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')} end + + extend self end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 8a48a9d394..c85ad12634 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -265,16 +265,4 @@ module ProjectsHelper "success" end end - - def github_import_enabled? - enabled_oauth_providers.include?(:github) - end - - def gitlab_import_enabled? - enabled_oauth_providers.include?(:gitlab) - end - - def bitbucket_import_enabled? - enabled_oauth_providers.include?(:bitbucket) - end end diff --git a/config/initializers/public_key.rb b/config/initializers/public_key.rb new file mode 100644 index 0000000000..d27e6519d1 --- /dev/null +++ b/config/initializers/public_key.rb @@ -0,0 +1,2 @@ +path = File.expand_path("~/.ssh/id_rsa.pub") +Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path) \ No newline at end of file diff --git a/lib/gitlab/bitbucket_import.rb b/lib/gitlab/bitbucket_import.rb new file mode 100644 index 0000000000..0e53972ac5 --- /dev/null +++ b/lib/gitlab/bitbucket_import.rb @@ -0,0 +1,6 @@ +module Gitlab + module BitbucketImport + mattr_accessor :public_key + @public_key = nil + end +end \ No newline at end of file diff --git a/lib/gitlab/bitbucket_import/key_adder.rb b/lib/gitlab/bitbucket_import/key_adder.rb index 207811237b..7d0b5fbc8a 100644 --- a/lib/gitlab/bitbucket_import/key_adder.rb +++ b/lib/gitlab/bitbucket_import/key_adder.rb @@ -9,13 +9,16 @@ module Gitlab end def execute + return false unless BitbucketImport.public_key.present? + project_identifier = "#{repo["owner"]}/#{repo["slug"]}" return true if client.deploy_key(project_identifier) - - # TODO: Point to actual public key. - client.add_deploy_key(project_identifier, File.read("/Users/douwemaan/.ssh/id_rsa.pub")) + + client.add_deploy_key(project_identifier, BitbucketImport.public_key) true + rescue + false end end end From f2b37de54ba3cb0a375fb3a03e7ffd1f18444c39 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Feb 2015 08:21:30 +0100 Subject: [PATCH 043/109] Fix specs. --- config/initializers/public_key.rb | 2 +- lib/gitlab/bitbucket_import.rb | 2 +- spec/controllers/import/bitbucket_controller_spec.rb | 1 + spec/controllers/import/github_controller_spec.rb | 1 + spec/controllers/import/gitlab_controller_spec.rb | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/initializers/public_key.rb b/config/initializers/public_key.rb index d27e6519d1..75d74e3625 100644 --- a/config/initializers/public_key.rb +++ b/config/initializers/public_key.rb @@ -1,2 +1,2 @@ path = File.expand_path("~/.ssh/id_rsa.pub") -Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path) \ No newline at end of file +Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path) diff --git a/lib/gitlab/bitbucket_import.rb b/lib/gitlab/bitbucket_import.rb index 0e53972ac5..7298152e7e 100644 --- a/lib/gitlab/bitbucket_import.rb +++ b/lib/gitlab/bitbucket_import.rb @@ -3,4 +3,4 @@ module Gitlab mattr_accessor :public_key @public_key = nil end -end \ No newline at end of file +end diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index 84e37ae560..5dd4124061 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -5,6 +5,7 @@ describe Import::BitbucketController do before do sign_in(user) + controller.stub(:bitbucket_import_enabled?).and_return(true) end describe "GET callback" do diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index 3b779855d3..b882041340 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -5,6 +5,7 @@ describe Import::GithubController do before do sign_in(user) + controller.stub(:github_import_enabled?).and_return(true) end describe "GET callback" do diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 287aa315db..b6b86b1bce 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -5,6 +5,7 @@ describe Import::GitlabController do before do sign_in(user) + controller.stub(:gitlab_import_enabled?).and_return(true) end describe "GET callback" do From 6979b3afd50f86550e523ed66ef22fd153e6cbc8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Feb 2015 17:00:26 +0100 Subject: [PATCH 044/109] Delete deploy key from Bitbucket after importing. --- app/workers/repository_import_worker.rb | 36 +++++++++++----------- lib/gitlab/bitbucket_import/client.rb | 19 +++++++++--- lib/gitlab/bitbucket_import/key_adder.rb | 2 -- lib/gitlab/bitbucket_import/key_deleter.rb | 23 ++++++++++++++ 4 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 lib/gitlab/bitbucket_import/key_deleter.rb diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index d7e759fb47..437640d230 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -6,27 +6,27 @@ class RepositoryImportWorker def perform(project_id) project = Project.find(project_id) - result = gitlab_shell.send(:import_repository, + + import_result = gitlab_shell.send(:import_repository, project.path_with_namespace, project.import_url) + return project.import_fail unless import_result - result_of_data_import = if project.import_type == 'github' - Gitlab::GithubImport::Importer.new(project).execute - elsif project.import_type == 'gitlab' - Gitlab::GitlabImport::Importer.new(project).execute - elsif project.import_type == 'bitbucket' - Gitlab::BitbucketImport::Importer.new(project).execute - else - true - end + data_import_result = if project.import_type == 'github' + Gitlab::GithubImport::Importer.new(project).execute + elsif project.import_type == 'gitlab' + Gitlab::GitlabImport::Importer.new(project).execute + elsif project.import_type == 'bitbucket' + Gitlab::BitbucketImport::Importer.new(project).execute + else + true + end + return project.import_fail unless data_import_result - if result && result_of_data_import - project.import_finish - project.save - project.satellite.create unless project.satellite.exists? - project.update_repository_size - else - project.import_fail - end + project.import_finish + project.save + project.satellite.create unless project.satellite.exists? + project.update_repository_size + Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' end end diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index 3d2ef78ee7..5095e592ab 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -61,17 +61,28 @@ module Gitlab JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}").body) end - def deploy_key(project_identifier) - JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find { |key| key["label"] =~ /GitLab/ } + def find_deploy_key(project_identifier, key) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find { |deploy_key| + deploy_key["key"].chomp == key.chomp + } end def add_deploy_key(project_identifier, key) + deploy_key = find_deploy_key(project_identifier, key) + return if deploy_key + JSON.parse(api.post("/api/1.0/repositories/#{project_identifier}/deploy-keys", key: key, label: "GitLab import key").body) end + def delete_deploy_key(project_identifier, key) + deploy_key = find_deploy_key(project_identifier, key) + return unless deploy_key + + api.delete("/api/1.0/repositories/#{project_identifier}/deploy-keys/#{deploy_key["pk"]}").code == "204" + end + def projects - JSON.parse(api.get("/api/1.0/user/repositories").body). - select { |repo| repo["scm"] == "git" } + JSON.parse(api.get("/api/1.0/user/repositories").body).select { |repo| repo["scm"] == "git" } end private diff --git a/lib/gitlab/bitbucket_import/key_adder.rb b/lib/gitlab/bitbucket_import/key_adder.rb index 7d0b5fbc8a..9931aa7e02 100644 --- a/lib/gitlab/bitbucket_import/key_adder.rb +++ b/lib/gitlab/bitbucket_import/key_adder.rb @@ -12,8 +12,6 @@ module Gitlab return false unless BitbucketImport.public_key.present? project_identifier = "#{repo["owner"]}/#{repo["slug"]}" - return true if client.deploy_key(project_identifier) - client.add_deploy_key(project_identifier, BitbucketImport.public_key) true diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb new file mode 100644 index 0000000000..1a24a86fc3 --- /dev/null +++ b/lib/gitlab/bitbucket_import/key_deleter.rb @@ -0,0 +1,23 @@ +module Gitlab + module BitbucketImport + class KeyDeleter + attr_reader :project, :current_user, :client + + def initialize(project) + @project = project + @current_user = project.creator + @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + end + + def execute + return false unless BitbucketImport.public_key.present? + + client.delete_deploy_key(project.import_source, BitbucketImport.public_key) + + true + rescue + false + end + end + end +end From a938b3eeb4684f5747f63c23385f870ccbf43d2d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Feb 2015 17:00:39 +0100 Subject: [PATCH 045/109] Link to original repo on import status pages. --- app/views/import/bitbucket/status.html.haml | 6 ++++-- app/views/import/github/status.html.haml | 9 +++++++-- app/views/import/gitlab/status.html.haml | 11 ++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 7a2613e4b0..cb8c29259c 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -17,7 +17,8 @@ %tbody - @already_added_projects.each do |project| %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td= project.import_source + %td + = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank" %td %strong= link_to project.path_with_namespace, project %td.job-status @@ -33,7 +34,8 @@ - @repos.each do |repo| %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} - %td= "#{repo["owner"]}/#{repo["slug"]}" + %td + = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank" %td.import-target = "#{repo["owner"]}/#{repo["slug"]}" %td.import-actions.job-status diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index b1538b1a41..dc8ec5e7ae 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -17,7 +17,8 @@ %tbody - @already_added_projects.each do |project| %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td= project.import_source + %td + = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank" %td %strong= link_to project.path_with_namespace, project %td.job-status @@ -25,12 +26,16 @@ %span.cgreen %i.fa.fa-check done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started - else = project.human_import_status_name - @repos.each do |repo| %tr{id: "repo_#{repo.id}"} - %td= repo.full_name + %td + = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank" %td.import-target = repo.full_name %td.import-actions.job-status diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 43db102994..841e660b08 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -12,12 +12,13 @@ %thead %tr %th From GitLab.com - %th To GitLab private instance + %th To this GitLab instance %th Status %tbody - @already_added_projects.each do |project| %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td= project.import_source + %td + = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" %td %strong= link_to project.path_with_namespace, project %td.job-status @@ -25,12 +26,16 @@ %span.cgreen %i.fa.fa-check done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started - else = project.human_import_status_name - @repos.each do |repo| %tr{id: "repo_#{repo["id"]}"} - %td= repo["path_with_namespace"] + %td + = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank" %td.import-target = repo["path_with_namespace"] %td.import-actions.job-status From 3fde1dce1f9058d4b57d17eac55051fb174c6aa4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Feb 2015 18:10:22 +0100 Subject: [PATCH 046/109] Satisfy Rubocop. --- lib/gitlab/bitbucket_import/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index 5095e592ab..c907bebaef 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -62,9 +62,9 @@ module Gitlab end def find_deploy_key(project_identifier, key) - JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find { |deploy_key| + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find do |deploy_key| deploy_key["key"].chomp == key.chomp - } + end end def add_deploy_key(project_identifier, key) From 20691df230332022cb4d5008d84c7ee6e6c8dbfd Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Feb 2015 22:42:52 +0100 Subject: [PATCH 047/109] Add Bitbucket integration docs. --- app/views/import/bitbucket/status.html.haml | 2 +- app/views/import/github/status.html.haml | 2 +- app/views/import/gitlab/status.html.haml | 2 +- .../_bitbucket_import_modal.html.haml | 10 +- .../projects/_github_import_modal.html.haml | 10 +- .../projects/_gitlab_import_modal.html.haml | 10 +- doc/integration/bitbucket.m | 1 - doc/integration/bitbucket.md | 97 +++++++++++++++++++ doc/integration/github.md | 29 +++--- doc/integration/gitlab.md | 18 ++-- doc/integration/google.md | 14 +-- doc/integration/omniauth.md | 1 + doc/integration/twitter.md | 14 +-- 13 files changed, 163 insertions(+), 47 deletions(-) delete mode 100644 doc/integration/bitbucket.m create mode 100644 doc/integration/bitbucket.md diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index cb8c29259c..90c97393b5 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title %i.fa.fa-bitbucket - Import repositories from Bitbucket + Import projects from Bitbucket %p.light Select projects you want to import. diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index dc8ec5e7ae..957022f382 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title %i.fa.fa-github - Import repositories from GitHub + Import projects from GitHub %p.light Select projects you want to import. diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 841e660b08..db16168120 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title %i.fa.fa-heart - Import repositories from GitLab.com + Import projects from GitLab.com %p.light Select projects you want to import. diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml index dd7aacc7e6..5c52f91927 100644 --- a/app/views/projects/_bitbucket_import_modal.html.haml +++ b/app/views/projects/_bitbucket_import_modal.html.haml @@ -3,7 +3,11 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 GitHub OAuth import + %h3 Import projects from Bitbucket .modal-body - You need to setup integration with Bitbucket first. - = link_to 'How to setup integration with Bitbucket', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md' \ No newline at end of file + To enable importing projects from Bitbucket, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/butbucket.md'}. \ No newline at end of file diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml index 99325e6611..e88a0f7d68 100644 --- a/app/views/projects/_github_import_modal.html.haml +++ b/app/views/projects/_github_import_modal.html.haml @@ -3,7 +3,11 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 GitHub OAuth import + %h3 Import projects from GitHub .modal-body - You need to setup integration with GitHub first. - = link_to 'How to setup integration with GitHub', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md' \ No newline at end of file + To enable importing projects from GitHub, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'}. \ No newline at end of file diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml index e7503f023b..52212b6ae0 100644 --- a/app/views/projects/_gitlab_import_modal.html.haml +++ b/app/views/projects/_gitlab_import_modal.html.haml @@ -3,7 +3,11 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 GitLab OAuth import + %h3 Import projects from GitLab.com .modal-body - You need to setup integration with GitLab first. - = link_to 'How to setup integration with GitLab', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md' \ No newline at end of file + To enable importing projects from GitLab.com, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'}. \ No newline at end of file diff --git a/doc/integration/bitbucket.m b/doc/integration/bitbucket.m deleted file mode 100644 index 30404ce4c5..0000000000 --- a/doc/integration/bitbucket.m +++ /dev/null @@ -1 +0,0 @@ -TODO \ No newline at end of file diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md new file mode 100644 index 0000000000..9f24ad8c58 --- /dev/null +++ b/doc/integration/bitbucket.md @@ -0,0 +1,97 @@ +# Integrate your server with Bitbucket + +Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account. + +To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket. +Bitbucket will generate an application ID and secret key for you to use. + +1. Sign in to Bitbucket. + +1. Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you. + +1. Select "OAuth" in the left menu. + +1. Select "Add consumer". + +1. Provide the required details. + - Name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive. + - Application description: Fill this in if you wish. + - URL: The URL to your GitLab installation. 'https://gitlab.company.com' +1. Select "Save". + +1. You should now see a Key and Secret in the list of OAuth customers. + Keep this page open as you continue configuration. + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For instalations from source: + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. + +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "bitbucket", + "app_id" => "YOUR_KEY", + "app_secret" => "YOUR_APP_SECRET", + "url" => "https://bitbucket.org/" + } + ] + ``` + + For installation from source: + + ``` + - { name: 'bitbucket', app_id: 'YOUR_KEY', + app_secret: 'YOUR_APP_SECRET' } + ``` + +1. Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7. + +1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7. + +1. Save the configuration file. + +1. Restart GitLab for the changes to take effect. + +On the sign in page there should now be a Bitbucket icon below the regular sign in form. +Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application. +If everything goes well the user will be returned to GitLab and will be signed in. + +## Bitbucket project import + +To allow projects to be imported directly into GitLab, Bitbucket requires one extra setup step compared to GitHub and GitLab.com. + +Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key. + +GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/id_rsa.pub`, which will expand to `/home/git/.ssh/id_rsa.pub` in most configurations. + +If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: + +1. Create a new SSH key: + + ```sh + sudo -u git -H ssh-keygen + ``` + + Make sure to use an **empty passphrase**. + +2. Restart GitLab to allow it to find the new public key. + +You should now see the "Import projects from Bitbucket" option on the New Project page enabled. \ No newline at end of file diff --git a/doc/integration/github.md b/doc/integration/github.md index 137d7e9d63..a9f1bc31bb 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -1,6 +1,9 @@ -# GitHub OAuth2 OmniAuth Provider +# Integrate your server with GitHub -To enable the GitHub OmniAuth provider you must register your application with GitHub. GitHub will generate a client ID and secret key for you to use. +Import projects from GitHub and login to your GitLab instance with your GitHub account. + +To enable the GitHub OmniAuth provider you must register your application with GitHub. +GitHub will generate an application ID and secret key for you to use. 1. Sign in to GitHub. @@ -17,7 +20,9 @@ To enable the GitHub OmniAuth provider you must register your application with G - Authorization callback URL: 'https://gitlab.company.com/' 1. Select "Register application". -1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). Keep this page open as you continue configuration. ![GitHub app](github_app.png) +1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). + Keep this page open as you continue configuration. + ![GitHub app](github_app.png) 1. On your GitLab server, open the configuration file. @@ -35,7 +40,7 @@ To enable the GitHub OmniAuth provider you must register your application with G sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for inital settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. 1. Add the provider configuration: @@ -45,8 +50,8 @@ To enable the GitHub OmniAuth provider you must register your application with G gitlab_rails['omniauth_providers'] = [ { "name" => "github", - "app_id" => "YOUR APP ID", - "app_secret" => "YOUR APP SECRET", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", "url" => "https://github.com/", "args" => { "scope" => "user:email" } } } @@ -56,17 +61,19 @@ To enable the GitHub OmniAuth provider you must register your application with G For installation from source: ``` - - { name: 'github', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET', + - { name: 'github', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', args: { scope: 'user:email' } } ``` -1. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7. +1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7. -1. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7. +1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7. 1. Save the configuration file. 1. Restart GitLab for the changes to take effect. -On the sign in page there should now be a GitHub icon below the regular sign in form. Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in. +On the sign in page there should now be a GitHub icon below the regular sign in form. +Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. +If everything goes well the user will be returned to GitLab and will be signed in. \ No newline at end of file diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md index 87400bed5b..49ffaa62af 100644 --- a/doc/integration/gitlab.md +++ b/doc/integration/gitlab.md @@ -3,7 +3,7 @@ Import projects from GitLab.com and login to your GitLab instance with your GitLab.com account. To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com. -GitLab.com will generate a application ID and secret key for you to use. +GitLab.com will generate an application ID and secret key for you to use. 1. Sign in to GitLab.com @@ -46,7 +46,7 @@ GitLab.com will generate a application ID and secret key for you to use. sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for inital settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. 1. Add the provider configuration: @@ -56,8 +56,8 @@ GitLab.com will generate a application ID and secret key for you to use. gitlab_rails['omniauth_providers'] = [ { "name" => "gitlab", - "app_id" => "YOUR APP ID", - "app_secret" => "YOUR APP SECRET", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", "args" => { "scope" => "api" } } } ] @@ -66,14 +66,14 @@ GitLab.com will generate a application ID and secret key for you to use. For installations from source: ``` - - { name: 'gitlab', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET', + - { name: 'gitlab', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', args: { scope: 'api' } } ``` -1. Change 'YOUR APP ID' to the Application ID from the GitLab application page. +1. Change 'YOUR_APP_ID' to the Application ID from the GitLab.com application page. -1. Change 'YOUR APP SECRET' to the secret from the GitLab application page. +1. Change 'YOUR_APP_SECRET' to the secret from the GitLab.com application page. 1. Save the configuration file. @@ -81,4 +81,4 @@ GitLab.com will generate a application ID and secret key for you to use. On the sign in page there should now be a GitLab.com icon below the regular sign in form. Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application. -If everything goes well the user will be returned to your GitLab instance and will be signed in. +If everything goes well the user will be returned to your GitLab instance and will be signed in. \ No newline at end of file diff --git a/doc/integration/google.md b/doc/integration/google.md index 168077c277..d7b741ece6 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -43,7 +43,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for inital settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. 1. Add the provider configuration: @@ -53,8 +53,8 @@ To enable the Google OAuth2 OmniAuth provider you must register your application gitlab_rails['omniauth_providers'] = [ { "name" => "google_oauth2", - "app_id" => "YOUR APP ID", - "app_secret" => "YOUR APP SECRET", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", "args" => { "access_type" => "offline", "approval_prompt" => '' } } } ] @@ -63,14 +63,14 @@ To enable the Google OAuth2 OmniAuth provider you must register your application For installations from source: ``` - - { name: 'google_oauth2', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET', + - { name: 'google_oauth2', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', args: { access_type: 'offline', approval_prompt: '' } } ``` -1. Change 'YOUR APP ID' to the client ID from the Google Developer page from step 10. +1. Change 'YOUR_APP_ID' to the client ID from the Google Developer page from step 10. -1. Change 'YOUR APP SECRET' to the client secret from the Google Developer page from step 10. +1. Change 'YOUR_APP_SECRET' to the client secret from the Google Developer page from step 10. 1. Save the configuration file. diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index c92fa3ee4b..24f7b4bb4b 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -70,6 +70,7 @@ Now we can choose one or more of the Supported Providers below to continue confi ## Supported Providers - [GitHub](github.md) +- [Bitbucket](bitbucket.md) - [GitLab.com](gitlab.md) - [Google](google.md) - [Shibboleth](shibboleth.md) diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md index 2d517b2fbc..fe9091ad9a 100644 --- a/doc/integration/twitter.md +++ b/doc/integration/twitter.md @@ -47,7 +47,7 @@ To enable the Twitter OmniAuth provider you must register your application with sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for inital settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. 1. Add the provider configuration: @@ -57,8 +57,8 @@ To enable the Twitter OmniAuth provider you must register your application with gitlab_rails['omniauth_providers'] = [ { "name" => "twitter", - "app_id" => "YOUR APP ID", - "app_secret" => "YOUR APP SECRET" + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET" } ] ``` @@ -66,13 +66,13 @@ To enable the Twitter OmniAuth provider you must register your application with For installations from source: ``` - - { name: 'twitter', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET' } + - { name: 'twitter', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET' } ``` -1. Change 'YOUR APP ID' to the API key from Twitter page in step 11. +1. Change 'YOUR_APP_ID' to the API key from Twitter page in step 11. -1. Change 'YOUR APP SECRET' to the API secret from the Twitter page in step 11. +1. Change 'YOUR_APP_SECRET' to the API secret from the Twitter page in step 11. 1. Save the configuration file. From bc80efb1fd2978a328fdc89a3164f60bcf7ed604 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 21 Feb 2015 11:07:35 +0100 Subject: [PATCH 048/109] Bring Gitorious import page in line with others. --- app/views/import/gitorious/status.html.haml | 11 ++++++++--- app/views/projects/new.html.haml | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index 35ed0a717d..c7617ca43d 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title %i.fa.fa-gitorious - Import repositories from Gitorious.org + Import projects from Gitorious %p.light Select projects you want to import. @@ -17,7 +17,8 @@ %tbody - @already_added_projects.each do |project| %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td= project.import_source + %td + = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" %td %strong= link_to project.path_with_namespace, project %td.job-status @@ -25,12 +26,16 @@ %span.cgreen %i.fa.fa-check done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started - else = project.human_import_status_name - @repos.each do |repo| %tr{id: "repo_#{repo.id}"} - %td= repo.full_name + %td + = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank" %td.import-target = repo.full_name %td.import-actions.job-status diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 875c092fd1..f3d166ffb8 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -85,7 +85,7 @@ .col-sm-10 = link_to new_import_gitorious_path do %i.fa.fa-heart - Import projects from Gitorious.org + Import projects from Gitorious %hr.prepend-botton-10 From 16c767814a921ab0d7ad3c551bb439a9e270f7b7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 21 Feb 2015 11:08:05 +0100 Subject: [PATCH 049/109] Re-enable rescuing from Bitbucket OAuth errors. --- app/controllers/import/bitbucket_controller.rb | 2 +- config/routes.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 89de5c5205..83ebc5fddc 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -2,7 +2,7 @@ class Import::BitbucketController < Import::BaseController before_filter :verify_bitbucket_import_enabled before_filter :bitbucket_auth, except: :callback - # rescue_from OAuth::Error, with: :bitbucket_unauthorized + rescue_from OAuth::Error, with: :bitbucket_unauthorized def callback request_token = session.delete(:oauth_request_token) diff --git a/config/routes.rb b/config/routes.rb index 57964bdc3b..450895cbdb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -73,6 +73,7 @@ Gitlab::Application.routes.draw do get :callback get :jobs end + resource :gitorious, only: [:create, :new], controller: :gitorious do get :status get :callback From fcd3e626b085f6d16ff2c780093bafa898647a85 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 21 Feb 2015 11:08:30 +0100 Subject: [PATCH 050/109] Move CHANGELOG entry. --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5a3a2ca2e2..726a415e80 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ v 7.9.0 (unreleased) - Improve error messages for file edit failures - Improve UI for commits, issues and merge request lists - Fix commit comments on first line of diff not rendering in Merge Request Discussion view. + - Add Bitbucket omniauth provider. + - Add Bitbucket importer. v 7.8.0 - Fix access control and protection against XSS for note attachments and other uploads. @@ -72,8 +74,6 @@ v 7.8.0 - Improve database performance for GitLab - Add Asana service (Jeremy Benoist) - Improve project web hooks with extra data - - Add Bitbucket omniauth provider. - - Add Bitbucket importer. v 7.7.2 - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch From 46bcf40b2cbc818837c855bebaef1b621e7c5283 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 24 Feb 2015 15:08:42 +0100 Subject: [PATCH 051/109] Add ".org" back to Gitorious mentions. --- app/views/import/gitorious/status.html.haml | 4 ++-- app/views/projects/new.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index c7617ca43d..7f1456fef5 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title %i.fa.fa-gitorious - Import projects from Gitorious + Import projects from Gitorious.org %p.light Select projects you want to import. @@ -11,7 +11,7 @@ %table.table.import-jobs %thead %tr - %th From Gitorious + %th From Gitorious.org %th To GitLab %th Status %tbody diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index f3d166ffb8..875c092fd1 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -85,7 +85,7 @@ .col-sm-10 = link_to new_import_gitorious_path do %i.fa.fa-heart - Import projects from Gitorious + Import projects from Gitorious.org %hr.prepend-botton-10 From 769d1ce64bdb7c9fd3981c65bc25dd14eb176c1e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 24 Feb 2015 16:10:55 +0100 Subject: [PATCH 052/109] Fix spec. --- spec/controllers/projects/uploads_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 8774ee0a84..029f48b2d7 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -25,7 +25,7 @@ describe Projects::UploadsController do context 'with valid image' do before do post :create, - namespace_id: project.namespace.to_param + namespace_id: project.namespace.to_param, project_id: project.to_param, file: jpg, format: :json From 1bf9fa8c7fc027b5273c143949e57eac9bef52a4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 24 Feb 2015 16:28:23 +0100 Subject: [PATCH 053/109] Exclude forks from profile contributions list. --- app/controllers/users_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4c2fe4c3c8..8a13394dba 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -6,7 +6,9 @@ class UsersController < ApplicationController def show @contributed_projects = Project. where(id: authorized_projects_ids & @user.contributed_projects_ids). - in_group_namespace.includes(:namespace) + in_group_namespace. + includes(:namespace). + reject(&:forked?) @projects = @user.personal_projects. where(id: authorized_projects_ids).includes(:namespace) From ea31726781296efa9c2493c3f01aa62ca77b45fe Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Tue, 24 Feb 2015 09:06:59 -0800 Subject: [PATCH 054/109] Added a margin and a couple styles Added a background, rather than an outline, which should reduce clutter on the screen. --- app/assets/stylesheets/sections/events.scss | 10 ++++++++-- app/helpers/events_helper.rb | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index b761451321..a477359dc8 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -184,6 +184,12 @@ } } -.event_filter li a { - padding: 5px 10px; +.event_filter { + + li a { + padding: 5px 10px; + background: rgba(0,0,0,0.045); + margin-left: 4px; + } + } diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index db0d4a2661..063916a8df 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -30,7 +30,7 @@ module EventsHelper end content_tag :li, class: "filter_icon #{active}" do - link_to request.path, class: 'btn has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do + link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) end end From f7c948223d000e4488d864d50f3292a6c7afeaf7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Feb 2015 09:08:34 -0800 Subject: [PATCH 055/109] Fix access to attachments uploaded with 'Choose file' button for public access --- app/controllers/files_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index 9671245d3f..a130bcba9c 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -1,4 +1,6 @@ class FilesController < ApplicationController + skip_before_filter :authenticate_user!, :reject_blocked + def download note = Note.find(params[:id]) uploader = note.attachment From 5179c5830bb3d8602eb25cc00f53be697c6010ac Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 24 Feb 2015 16:28:40 +0100 Subject: [PATCH 056/109] Contributed projects either have user pushes or created MRs. --- app/models/user.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 21ccc76978..0c133f0e1e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -618,9 +618,10 @@ class User < ActiveRecord::Base def contributed_projects_ids Event.where(author_id: self). where("created_at > ?", Time.now - 1.year). - code_push. + where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)", + pushed: Event::PUSHED, created: Event::CREATED). reorder(project_id: :desc). - select('DISTINCT(project_id)'). - map(&:project_id) + select(:project_id). + uniq end end From 5b6d6bbc195c438de0babb9a1f0b6971bbd5673e Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Tue, 24 Feb 2015 09:42:35 -0800 Subject: [PATCH 057/109] Added square caret to Back to Settings button This was done to show difference between Open/Close toggle, which was using the same arrow as Back to Settings button previously. --- app/views/layouts/nav/_project.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 15b489c7d9..ef31537b84 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -2,7 +2,7 @@ - if @project_settings_nav = nav_link do = link_to namespace_project_path(@project.namespace, @project), title: 'Back to project', class: "" do - %i.fa.fa-angle-left + %i.fa.fa-caret-square-o-left %span Back to project From 9c4337e58bf8a5e2c9085a1f4eff0e942d659d2d Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Tue, 24 Feb 2015 10:01:41 -0800 Subject: [PATCH 058/109] Fixed tests Tests expected specific string capitalization for headers. --- features/steps/profile/notifications.rb | 2 +- features/steps/profile/profile.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb index df96dddd06..13e93618eb 100644 --- a/features/steps/profile/notifications.rb +++ b/features/steps/profile/notifications.rb @@ -7,6 +7,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps end step 'I should see global notifications settings' do - page.should have_content "Notifications settings" + page.should have_content "Notifications Settings" end end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index a907b0b7dc..3cba2ae100 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -3,7 +3,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps include SharedPaths step 'I should see my profile info' do - page.should have_content "Profile settings" + page.should have_content "Profile Settings" end step 'I change my profile info' do From 53e404144c8ce9f602ec1e3a642d5d3971dc8217 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 24 Feb 2015 20:24:30 +0200 Subject: [PATCH 059/109] changelog && documentation --- CHANGELOG | 1 + doc/workflow/web_editor.md | 4 ++-- doc/workflow/web_editor/edit_file.png | Bin 99624 -> 89039 bytes doc/workflow/web_editor/new_file.png | Bin 100516 -> 85526 bytes 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d879ee8572..0186c03469 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 7.9.0 (unreleased) - Improve error messages for file edit failures - Improve UI for commits, issues and merge request lists - Fix commit comments on first line of diff not rendering in Merge Request Discussion view. + - Save web edit in new branch v 7.8.0 - Fix access control and protection against XSS for note attachments and other uploads. diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md index bcadf5e8c0..7fc8f96b9e 100644 --- a/doc/workflow/web_editor.md +++ b/doc/workflow/web_editor.md @@ -10,7 +10,7 @@ to create the first file and open it in the web editor. ![web editor 1](web_editor/empty_project.png) -Fill in a file name, some content, a commit message and press the commit button. +Fill in a file name, some content, a commit message, branch name and press the commit button. The file will be saved to the repository. ![web editor 2](web_editor/new_file.png) @@ -21,6 +21,6 @@ viewing the file. ![web editor 3](web_editor/show_file.png) Editing a file is almost the same as creating a new file, -with as addition the ability to preview your changes in a separate tab. +with as addition the ability to preview your changes in a separate tab. Also you can save your change to another branch by filling out field `branch` ![web editor 3](web_editor/edit_file.png) diff --git a/doc/workflow/web_editor/edit_file.png b/doc/workflow/web_editor/edit_file.png index 1522c50b62fcf5d4fd1c3d0abdc1232e8e09ca98..f480c69ac3eafbbfd85b30ca0b1324d824f89409 100644 GIT binary patch literal 89039 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 BY?c53 literal 99624 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 SJd-Wp-!)aO%Xyb>-T5!0{2BlN diff --git a/doc/workflow/web_editor/new_file.png b/doc/workflow/web_editor/new_file.png index 80941f37cea0c50d4e16f9df0460cd0fd705b36d..55ebd9e025720ed108f16d5b6d61926701510c10 100644 GIT binary patch literal 85526 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 literal 100516 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 UybEaf3;dipeeP8DN!#213x@N$$^ZZW From 545f12d548c65d2dde966c29ee518df515732ae6 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 24 Feb 2015 20:46:52 +0200 Subject: [PATCH 060/109] update gitlab-linguist --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 093f7bacc0..afe4ad4dda 100644 --- a/Gemfile +++ b/Gemfile @@ -50,7 +50,7 @@ gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap" gem 'gollum-lib', '~> 4.0.0' # Language detection -gem "gitlab-linguist", "~> 3.0.0", require: "linguist" +gem "gitlab-linguist", "~> 3.0.1", require: "linguist" # API gem "grape", "~> 0.6.1" diff --git a/Gemfile.lock b/Gemfile.lock index 4bc47836e7..602e8f0afd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -190,7 +190,7 @@ GEM diff-lcs (~> 1.1) mime-types (~> 1.15) posix-spawn (~> 0.3) - gitlab-linguist (3.0.0) + gitlab-linguist (3.0.1) charlock_holmes (~> 0.6.6) escape_utils (~> 0.2.4) mime-types (~> 1.19) @@ -669,7 +669,7 @@ DEPENDENCIES github-markup gitlab-flowdock-git-hook (~> 0.4.2) gitlab-grack (~> 2.0.0.pre) - gitlab-linguist (~> 3.0.0) + gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.0.1.1) gitlab_git (= 7.0.0.rc14) gitlab_meta (= 7.0) From 75048fcd6917cfc25795bcca0929d442f9c65be4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Feb 2015 11:46:42 -0800 Subject: [PATCH 061/109] Revert "Update charlock_holmes to 0.7.3" This reverts commit c217860ae74381d75a4932fabb4417ac08b014b4. --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 602e8f0afd..7af8f7abb6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,7 +78,7 @@ GEM json (>= 1.7) celluloid (0.16.0) timers (~> 4.0.0) - charlock_holmes (0.7.3) + charlock_holmes (0.6.9.4) cliver (0.3.2) coderay (1.1.0) coercible (1.0.0) From c6e9d14ceb03d4f1cf2d3a720f1e64c41d3c43b8 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 24 Feb 2015 20:30:30 +0100 Subject: [PATCH 062/109] Update version sorter to 2.0.0, fixes #8572 --- Gemfile.lock | 2 +- spec/helpers/application_helper_spec.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index dc0285255c..e28a577c09 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -608,7 +608,7 @@ GEM raindrops (~> 0.7) unicorn-worker-killer (0.4.2) unicorn (~> 4) - version_sorter (1.1.0) + version_sorter (2.0.0) virtus (1.0.1) axiom-types (~> 0.0.5) coercible (~> 1.0) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 9d99b6e33c..6abf5b9813 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -192,10 +192,12 @@ describe ApplicationHelper do it 'sorts tags in a natural order' do # Stub repository.tag_names to make sure we get some valid testing data expect(@project.repository).to receive(:tag_names). - and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v1.0.9a']) + and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v1.0.9a', + 'v2.0-rc1', 'v2.0rc2']) expect(options[1][1]). - to eq(['v3.1.4.2', 'v2.0', 'v1.0.10', 'v1.0.9a', 'v1.0.9']) + to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0-rc1', 'v1.0.10', 'v1.0.9', + 'v1.0.9a']) end end From 56f51bed6b166f12b8b0f4f1bd883142c5e079a6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 25 Feb 2015 00:04:48 +0100 Subject: [PATCH 063/109] Expand Bitbucket integration docs. --- doc/integration/bitbucket.md | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 9f24ad8c58..cc6389f5aa 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -76,22 +76,46 @@ If everything goes well the user will be returned to GitLab and will be signed i ## Bitbucket project import -To allow projects to be imported directly into GitLab, Bitbucket requires one extra setup step compared to GitHub and GitLab.com. +To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com. Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key. -GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/id_rsa.pub`, which will expand to `/home/git/.ssh/id_rsa.pub` in most configurations. +### Step 1: Known hosts + +To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so: + +1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use: + + ```sh + ssh git@bitbucket.org + ``` + +1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter): + + ```sh + The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established. + RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40. + Are you sure you want to continue connecting (yes/no)? + ``` + +1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts. + +1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2: + +### Step 2: Public key + +To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/id_rsa.pub`, which will expand to `/home/git/.ssh/id_rsa.pub` in most configurations. If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: 1. Create a new SSH key: ```sh - sudo -u git -H ssh-keygen + sudo -u git -H ssh-keygen ``` Make sure to use an **empty passphrase**. 2. Restart GitLab to allow it to find the new public key. -You should now see the "Import projects from Bitbucket" option on the New Project page enabled. \ No newline at end of file +You should now see the "Import projects from Bitbucket" option on the New Project page enabled. From 4aeb9165660348d862172965238c366afaca05d5 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 24 Feb 2015 16:53:59 -0800 Subject: [PATCH 064/109] Update changelog for 7.8.1 --- CHANGELOG | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f67b45c058..7a5e2c2fc7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,13 @@ v 7.9.0 (unreleased) - Save web edit in new branch - Fix ordering of imported but unchanged projects (Marco Wessel) +v 7.8.1 + - Fix run of custom post receive hooks + - Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3 + - Fix the warning for LDAP users about need to set password + - Fix avatars which were not shown for non logged in users + - Fix urls for the issues when relative url was enabled + v 7.8.0 - Fix access control and protection against XSS for note attachments and other uploads. - Replace highlight.js with rouge-fork rugments (Stefan Tatschner) @@ -40,7 +47,7 @@ v 7.8.0 - Allow configuring protection of the default branch upon first push (Marco Wessel) - Add gitlab.com importer - Add an ability to login with gitlab.com - - Add a commit calendar to the user profile (Hannes Rosenögger) + - Add a commit calendar to the user profile (Hannes Rosenögger) - Submit comment on command-enter - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`. - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" @@ -55,7 +62,7 @@ v 7.8.0 - API: Access groups with their path (Julien Bianchi) - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard) - Allow notification email to be set separately from primary email. - - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) + - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) - Don't have Markdown preview fail for long comments/wiki pages. - When test web hook - show error message instead of 500 error page if connection to hook url was reset - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) From f43c0429c25dff42aec395a24057eb8d37ff449d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Feb 2015 21:31:54 -0800 Subject: [PATCH 065/109] Improve admin projects and users pages for mobile devices --- CHANGELOG | 1 + app/assets/stylesheets/generic/mobile.scss | 1 + app/views/admin/projects/index.html.haml | 6 ++++-- app/views/admin/users/index.html.haml | 6 ++++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7a5e2c2fc7..9059168475 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 7.9.0 (unreleased) - Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev) - Save web edit in new branch - Fix ordering of imported but unchanged projects (Marco Wessel) + - Mobile UI improvements: make aside content expandable v 7.8.1 - Fix run of custom post receive hooks diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index 2bb69f4aa7..b3727c3367 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -69,5 +69,6 @@ background: #EEE; font-size: 20px; color: #777; + z-index: 100; @include box-shadow(0 1px 2px #DDD); } diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 0f9cdfc9e8..3780500a44 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,5 +1,7 @@ .row - .col-md-3 + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %aside.col-md-3 .admin-filter = form_tag admin_namespaces_projects_path, method: :get, class: '' do .form-group @@ -36,7 +38,7 @@ = button_tag "Search", class: "btn submit btn-primary" = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" - .col-md-9 + %section.col-md-9 .panel.panel-default .panel-heading Projects (#{@projects.total_count}) diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 6e15cec467..4a4f0549ad 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,5 +1,7 @@ .row - .col-md-3 + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %aside.col-md-3 .admin-filter %ul.nav.nav-pills.nav-stacked %li{class: "#{'active' unless params[:filter]}"} @@ -27,7 +29,7 @@ %hr = link_to 'Reset', admin_users_path, class: "btn btn-cancel" - .col-md-9 + %section.col-md-9 .panel.panel-default .panel-heading Users (#{@users.total_count}) From 6fe057cc7b4ec4c7483422ea0fe2b4eb28e315df Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Feb 2015 21:40:47 -0800 Subject: [PATCH 066/109] Fix header avatar size --- app/assets/stylesheets/sections/header.scss | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index e255cbcada..363bc2e9a2 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -86,7 +86,7 @@ header { .container { width: 100% !important; - padding-left: 0px; + padding: 0px; } /** @@ -134,14 +134,13 @@ header { } .profile-pic { - position: relative; - top: -1px; - padding-right: 0px !important; + padding: 0px !important; + width: 46px; + height: 46px; + margin-left: 5px; img { - width: 50px; - height: 50px; - margin: -15px; - margin-left: 5px; + width: 46px; + height: 46px; } } From 1faf3676aa023395c468d4e89224726c8e5b9b7d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Feb 2015 22:19:14 -0800 Subject: [PATCH 067/109] Refactor gitlab themes css --- app/assets/stylesheets/sections/header.scss | 62 ----------------- app/assets/stylesheets/themes/dark-theme.scss | 69 +++++++++++++++++++ app/assets/stylesheets/themes/ui_color.scss | 40 +---------- app/assets/stylesheets/themes/ui_gray.scss | 30 +------- app/assets/stylesheets/themes/ui_mars.scss | 36 +--------- app/assets/stylesheets/themes/ui_modern.scss | 40 +---------- 6 files changed, 77 insertions(+), 200 deletions(-) create mode 100644 app/assets/stylesheets/themes/dark-theme.scss diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index 363bc2e9a2..28fbe03ee7 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -173,68 +173,6 @@ header { @include transition(all 0.15s ease-in 0s); } } - - - /* - * Dark header - * - */ - &.header-dark { - &.navbar-gitlab { - .navbar-inner { - background: #708090; - border-bottom: 1px solid #AAA; - - .navbar-toggle { color: #fff; } - - .nav > li > a { - color: #AAA; - - &:hover, &:focus, &:active { - background: none; - color: #FFF; - } - } - } - } - - .turbolink-spinner { - color: #FFF; - } - - .search { - .search-input { - background-color: #D2D5DA; - background-color: rgba(255, 255, 255, 0.5); - border: 1px solid #AAA; - - &:focus { - background-color: white; - } - } - } - .search-input::-webkit-input-placeholder { - color: #666; - } - .app_logo { - a { - h1 { - background: image-url('logo-white.png') no-repeat center center; - background-size: 32px; - color: #fff; - } - } - } - .title { - a { - color: #FFF; - &:hover { - text-decoration: underline; - } - } - color: #fff; - } - } } .search .search-input { diff --git a/app/assets/stylesheets/themes/dark-theme.scss b/app/assets/stylesheets/themes/dark-theme.scss new file mode 100644 index 0000000000..abb1ba6686 --- /dev/null +++ b/app/assets/stylesheets/themes/dark-theme.scss @@ -0,0 +1,69 @@ +@mixin dark-theme($color-light, $color, $color-darker, $color-dark) { + header { + &.navbar-gitlab { + .navbar-inner { + background: $color; + + .navbar-toggle { + color: #FFF; + } + + .app_logo, .navbar-toggle { + &:hover { + background-color: $color-darker; + } + + h1 { + background: image-url('logo-white.png') no-repeat center center; + background-size: 32px; + color: #FFF; + } + } + + .app_logo { + background-color: $color-dark; + } + + .title { + color: #FFF; + + a { + color: #FFF; + &:hover { + text-decoration: underline; + } + } + } + + .search { + .search-input { + background-color: $color-light; + background-color: rgba(255, 255, 255, 0.5); + border: 1px solid $color-light; + + &:focus { + background-color: white; + } + } + } + + .search-input::-webkit-input-placeholder { + color: #666; + } + + .nav > li > a { + color: $color-light; + + &:hover, &:focus, &:active { + background: none; + color: #FFF; + } + } + + .search-input { + border-color: $color-light; + } + } + } + } +} diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss index 3c441a8e09..7ac6903b2e 100644 --- a/app/assets/stylesheets/themes/ui_color.scss +++ b/app/assets/stylesheets/themes/ui_color.scss @@ -1,42 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Violet GitLab UI theme */ .ui_color { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #548; - border-bottom: 1px solid #436; - .app_logo, .navbar-toggle { - &:hover { - background-color: #436; - } - } - .app_logo { - background-color: #325; - } - .nav > li > a { - color: #98C; - } - .search-input { - border-color: #98C; - } - } - } - } - - .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { - background: #659; - } + @include dark-theme(#98C, #548, #436, #325); } diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss index 8df08ccaee..9257e5f4d4 100644 --- a/app/assets/stylesheets/themes/ui_gray.scss +++ b/app/assets/stylesheets/themes/ui_gray.scss @@ -1,32 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Gray GitLab UI theme */ .ui_gray { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #373737; - border-bottom: 1px solid #272727; - .app_logo, .navbar-toggle { - &:hover { - background-color: #272727; - } - } - .app_logo { - background-color: #222; - } - } - } - } + @include dark-theme(#979797, #373737, #272727, #222222); } diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index b08cbda6c4..4caf5843d9 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -1,38 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Classic GitLab UI theme */ .ui_mars { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #474D57; - border-bottom: 1px solid #373D47; - .app_logo, .navbar-toggle { - &:hover { - background-color: #373D47; - } - } - .app_logo { - background-color: #24272D; - } - .nav > li > a { - color: #979DA7; - } - .search-input { - border-color: #979DA7; - } - } - } - } + @include dark-theme(#979DA7, #474D57, #373D47, #24272D); } diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss index 34f39614ca..7044988231 100644 --- a/app/assets/stylesheets/themes/ui_modern.scss +++ b/app/assets/stylesheets/themes/ui_modern.scss @@ -1,42 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Modern GitLab UI theme */ .ui_modern { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #019875; - border-bottom: 1px solid #019875; - .app_logo, .navbar-toggle { - &:hover { - background-color: #018865; - } - } - .app_logo { - background-color: #017855; - } - .nav > li > a { - color: #ADC; - } - .search-input { - border-color: #8ba; - } - } - } - } - - .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { - background: #019875; - } + @include dark-theme(#ADC, #019875, #018865, #017855); } From 6e559be6c68b921e12518816a824724564e3e315 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Feb 2015 22:58:47 -0800 Subject: [PATCH 068/109] Refactor header logo and setup expectation on it size --- app/assets/images/logo-black.png | Bin 2608 -> 3897 bytes app/assets/images/logo-white.png | Bin 7331 -> 7699 bytes app/assets/stylesheets/sections/header.scss | 18 +++++++----------- app/assets/stylesheets/themes/dark-theme.scss | 6 ------ app/helpers/appearances_helper.rb | 8 ++++++++ app/views/layouts/_head_panel.html.haml | 2 +- .../layouts/_public_head_panel.html.haml | 4 +--- 7 files changed, 17 insertions(+), 21 deletions(-) diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png index 49cdc16cacd9a6be58ad79813461554bed5db8e2..a58645ed7b0616bb2d9dec551969f782dd849e52 100644 GIT binary patch literal 3897 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 Hu0mjfBK$?6 literal 2608 zcmV-03eWY4P)wGOn5Sgs*xMZ{Da3_*}PSxDahySv#Hwcupi5-vg=UExjKi;sDGd^~)qLSe_67j6n0!=_N;QG>WLyiza`UkxvZ zb@(avu!B{o;B#SJXh$37*)b*-T#L8hNr{JI8C=E zW}q|t8uN^gS@>~yGdzl;_0zz9_!X8L#NzNug~o8MEmT+#o<~=~WvCdPChUph3$Db& zp$(fuCl(s$5WI-Z;ZA(9!n}g%h*1cqgbm?UTxr{m536y%L3}#Y!c2StGi;%ODsDu3 z_#@^T-ck55p2KVC3|-+>+!MZy^U!Q-P2uP03L8RuScRv<*D=}f?ni5Q60>YWSfX3l z6ux6y!g1)tVY(IEX%GdU!m3cm-`U!1ToZnex6qE;@%e(IG0EudjgxUH9>MF_i2n%Z z*xCZT7&hQSgDb+BVKz2|vkhWe*oaH@_!wpxT<{Rq;qEXA`-B_tdU!279?rC5Obp+^ zlUNr@{B^+;TphZ?%7TLpq2PQ>FE|zLm}3Y};aNSZc*HOYE=DU>;Z>{;-$%2ZqlyoN z-=Hn53MC$^P&I@KbMP%auEtB4Vld%v!^W_`ZsGWXlMQYW+CvQ+@VyHAdYgokuqMCoT$h2uoho43J-=C3!3$)U?vuajaZ8h+kN~f)Wg5)7QU-j_!Zh4 zm}aCZ9Dy#}t5=27u@3bK%(nY^Ahd>M`kWi;_>?g?FSKKEm1EEz-oQufz8*k3Zo&M} z5gxW3?hRe2hgN*n?r(XhhfXXv4t^Huc+fjQ!EDsAVJ6MCS9l=&5sltCJ{i{IOSaRW zhyTSFz4I)^v$jJeyn~P2GSos_cpY~Y9BO=>ie+IF>Y)}&lwkw@ z*$5OY2z7kOXcQcSu7d9xMz|mKP=+#;p$ugxLk;T+F0q{o3fzl&sD%frun1YT_h7$FHb9K9Xi6Xod zYG^CCtWvQkv|vraWFvD__@D6mJ@+!XvFiP(hY}mUa*aZ_YJ;QD5z0{FF(Y?7HZ^dB zAzTw$QKBAxIGMumeiG`TM2Y+K3jY;Kv=xO5b=zaAX`u{Djm*nvnLAgrZnLJEgpN=P z|E*WSmqQs!bcW9vnWJ$i{t#ASZ(FKxSXhi_(Gk93 zd>Ln<(9d3>BRqi^g9_(heW;+v1~O+}1QH~}lsiPmsSrQ*~|#p&Uz;rUPxWhl`e zzGfT3<0wNJ%20+n)`g|vCt)Sp!zSEQ;TlX1lfq<7#-z|>?2gk%vN`K*bhj;<>Q4V7@-h1;-d)PlqlXVH?7gC=33{`1=qh>Q!(gUc%aNq8=Z{G}};M zCDw!k^_s*qyw~{oVOU{sb3%!ejIRT+w_XMNhc$Q}GmX%cP)BQ(>G~XngN=_x1&a*r z{t5>fKXY(`KKq6`+A+f@9gMZOLORf2ulpKM_7wHY_ITHY{HeccAo82VLq-8H;0?U z&A74Pv&P3FRP<@YGoe;tl5uiJScek~G!>T^yM6ISsG}b0p&shkfO)ph1qFK<=sTem z#~DY>SdES;Owmt4Q-x0(n@XsMGSosDN|d1mlZ@TzVS;`NF2;IXYX_JaR^v%q2nXXF zW3y*i5jsK{+QMr%+!z&nCLE@pf>S~r_t{YjPC`fcbwSlY3&H|p_j%leOEJ+Hg^z~Q z40HrK@r0e=RFvUCTd4Ahu%EX{*c%HCG%s|8*Q!jllPnCK;itBCAr7^Z6&!@i4Rioj zg%>K!urr18u@MhespuzMjv026Mka@=^iyznXbsO`KW~$>Ly6y_qMz`OXtaY>Xuxgy z2`6APUahj1w^Nn*p$#k0sMjvbP=u zZVavAUc0cVSP@$BU47<;CC1OI=ZJ(T!_Kly(j!Be3tyb*52EPpakxDKb-h5R1{Mw@YG SrUcCZ0000N+hd^+5cf#R%xCeJTT;ILg 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 F|35^Y{uBTJ literal 7331 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 ".navbar-collapse", "data-toggle" => "collapse", type: "button"} diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index e912fea2ae..bd6bb3c720 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -2,10 +2,8 @@ .navbar-inner .container %div.app_logo - %span.separator = link_to explore_root_path, class: "home" do - %h1 GITLAB - %span.separator + = brand_header_logo %h1.title= title %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} From ee343661e18ccd95f2c74e7bc0d0116a100270ea Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Feb 2015 23:17:11 -0800 Subject: [PATCH 069/109] Get rid of black logo --- app/assets/images/logo-black.png | Bin 3897 -> 0 bytes app/assets/stylesheets/themes/ui_basic.scss | 11 +++++++++-- app/helpers/appearances_helper.rb | 6 +----- 3 files changed, 10 insertions(+), 7 deletions(-) delete mode 100644 app/assets/images/logo-black.png diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png deleted file mode 100644 index a58645ed7b0616bb2d9dec551969f782dd849e52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3897 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 Hu0mjfBK$?6 diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss index 0dad9917b5..097d5c5b73 100644 --- a/app/assets/stylesheets/themes/ui_basic.scss +++ b/app/assets/stylesheets/themes/ui_basic.scss @@ -10,8 +10,15 @@ background: #F1F1F1; border-bottom: 1px solid #DDD; - .app_logo { - background-color: #DDD; + .title { + color: #555; + + a { + color: #555; + &:hover { + text-decoration: underline; + } + } } .nav > li > a { diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index 21e8557abc..bb8d568380 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -16,10 +16,6 @@ module AppearancesHelper end def brand_header_logo - if theme_type == 'light_theme' - image_tag 'logo-black.png' - else - image_tag 'logo-white.png' - end + image_tag 'logo-white.png' end end From 878e86bf64ce09938c1f2cc4dd1555029969a7c2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Feb 2015 23:26:32 -0800 Subject: [PATCH 070/109] Remove unnecessary theme_type from body class --- app/views/layouts/admin.html.haml | 2 +- app/views/layouts/application.html.haml | 2 +- app/views/layouts/errors.html.haml | 2 +- app/views/layouts/explore.html.haml | 2 +- app/views/layouts/group.html.haml | 2 +- app/views/layouts/navless.html.haml | 2 +- app/views/layouts/profile.html.haml | 2 +- app/views/layouts/project_settings.html.haml | 2 +- app/views/layouts/projects.html.haml | 2 +- app/views/layouts/public_group.html.haml | 2 +- app/views/layouts/public_projects.html.haml | 2 +- app/views/layouts/public_users.html.haml | 2 +- app/views/layouts/search.html.haml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index e8751a6987..ab84e87c30 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Admin area" - %body{class: "#{app_theme} #{theme_type} admin", :'data-page' => body_data_page} + %body{class: "#{app_theme} admin", :'data-page' => body_data_page} = render "layouts/head_panel", title: link_to("Admin area", admin_root_path) = render 'layouts/page', sidebar: 'layouts/nav/admin' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 49123744ff..6bd8ac4adb 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Dashboard" - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page } + %body{class: "#{app_theme} application", :'data-page' => body_data_page } = render "layouts/head_panel", title: link_to("Dashboard", root_path) = render 'layouts/page', sidebar: 'layouts/nav/dashboard' diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index e7d875173e..e51fd4cb82 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Error" - %body{class: "#{app_theme} #{theme_type} application"} + %body{class: "#{app_theme} application"} = render "layouts/head_panel", title: "" if current_user .container.navless-container = render "layouts/flash" diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml index 09855b222d..2bd0b8d85c 100644 --- a/app/views/layouts/explore.html.haml +++ b/app/views/layouts/explore.html.haml @@ -2,7 +2,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: page_title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" - if current_user = render "layouts/head_panel", title: link_to(page_title, explore_root_path) diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index fa0ed317ce..f4a6bee15f 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: group_head_title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/head_panel", title: link_to(@group.name, group_path(@group)) = render 'layouts/page', sidebar: 'layouts/nav/group' diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml index a3b55542bf..4d0278251a 100644 --- a/app/views/layouts/navless.html.haml +++ b/app/views/layouts/navless.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" = render "layouts/head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title .container.navless-container diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 19d6efed78..2b5be7fc37 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Profile" - %body{class: "#{app_theme} #{theme_type} profile", :'data-page' => body_data_page} + %body{class: "#{app_theme} profile", :'data-page' => body_data_page} = render "layouts/head_panel", title: link_to("Profile", profile_path) = render 'layouts/page', sidebar: 'layouts/nav/profile' diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index d2c9c2a991..0a0039dec1 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace - %body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } + %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" - @project_settings_nav = true diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index c44a40c9c1..dde0964f47 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: project_head_title - %body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } + %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml index 4b69329b8f..b9b1d03e08 100644 --- a/app/views/layouts/public_group.html.haml +++ b/app/views/layouts/public_group.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: group_head_title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/public_head_panel", title: link_to(@group.name, group_path(@group)) = render 'layouts/page', sidebar: 'layouts/nav/group' diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml index 027e9a5313..04fa7c84e7 100644 --- a/app/views/layouts/public_projects.html.haml +++ b/app/views/layouts/public_projects.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/public_head_panel", title: project_title(@project) = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml index 3538a8b169..71c16bd168 100644 --- a/app/views/layouts/public_users.html.haml +++ b/app/views/layouts/public_users.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/public_head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title = render 'layouts/page' diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml index 177e2073a0..f9d8db06e1 100644 --- a/app/views/layouts/search.html.haml +++ b/app/views/layouts/search.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Search" - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" = render "layouts/head_panel", title: link_to("Search", search_path) .container.navless-container From 9b8d5c4ab4e5dc544891f37fa6dd91134577e313 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 25 Feb 2015 10:49:51 +0100 Subject: [PATCH 071/109] Make test element selection more specific. --- features/steps/project/merge_requests.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index d358f1d875..263f2ef243 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -213,7 +213,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see a comment like "Line is wrong" in the second file' do - within '.files [id^=diff]:nth-child(2) .note-text' do + within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do page.should have_visible_content "Line is wrong" end end @@ -225,7 +225,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see a comment like "Line is wrong here" in the second file' do - within '.files [id^=diff]:nth-child(2) .note-text' do + within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do page.should have_visible_content "Line is wrong here" end end @@ -238,7 +238,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps click_button "Add Comment" end - within ".files [id^=diff]:nth-child(1) .note-text" do + within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do page.should have_content "Line is correct" end end From 9c6f0487950f1b510d7222885ed8591607b4fe9b Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 25 Feb 2015 12:18:15 +0200 Subject: [PATCH 072/109] Fix GitLab importer. Hide already imported projects --- app/controllers/import/gitlab_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index a51ea36aff..c18178abf7 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -16,7 +16,7 @@ class Import::GitlabController < Import::BaseController @already_added_projects = current_user.created_projects.where(import_type: "gitlab") already_added_projects_names = @already_added_projects.pluck(:import_source) - @repos.to_a.reject!{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } + @repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } end def jobs From 00c631573f1f7564cfd8d823dce761fc6c76e2bc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 25 Feb 2015 15:16:24 +0100 Subject: [PATCH 073/109] Fix Gitorious import status page hiding of already added projects. --- app/controllers/import/gitorious_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb index 627b4a171b..6067a87ee0 100644 --- a/app/controllers/import/gitorious_controller.rb +++ b/app/controllers/import/gitorious_controller.rb @@ -15,7 +15,7 @@ class Import::GitoriousController < Import::BaseController @already_added_projects = current_user.created_projects.where(import_type: "gitorious") already_added_projects_names = @already_added_projects.pluck(:import_source) - @repos.to_a.reject! { |repo| already_added_projects_names.include? repo.full_name } + @repos.reject! { |repo| already_added_projects_names.include? repo.full_name } end def jobs From 183f521079873c0d0942f2ad72660822714d0b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Lafoucrie=CC=80re?= Date: Wed, 25 Feb 2015 10:14:10 -0500 Subject: [PATCH 074/109] Bump gemnasium service gem --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index dc0285255c..e4d43bb56b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -174,8 +174,8 @@ GEM dotenv (>= 0.7) thor (>= 0.13.6) formatador (0.2.4) - gemnasium-gitlab-service (0.2.3) - rugged (~> 0.19) + gemnasium-gitlab-service (0.2.4) + rugged (~> 0.21) gherkin-ruby (0.3.1) racc github-markup (1.3.1) @@ -489,7 +489,7 @@ GEM ruby-progressbar (1.7.1) rubyntlm (0.4.0) rubypants (0.2.0) - rugged (0.21.2) + rugged (0.21.4) rugments (1.0.0.beta3) safe_yaml (0.9.7) sanitize (2.1.0) From a672b4688309dc356922bd3f9c61b8ae9de018f8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 25 Feb 2015 17:34:01 +0100 Subject: [PATCH 075/109] Include number of affected people in all/group mention autocomplete item. --- app/services/projects/participants_service.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 0be50fed7c..f6f9aceef9 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -35,15 +35,21 @@ module Projects end def sorted(users) - users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } } + users.uniq.to_a.compact.sort_by(&:username).map do |user| + { username: user.username, name: user.name } + end end def groups - @user.authorized_groups.sort_by(&:path).map { |group| { username: group.path, name: group.name } } + @user.authorized_groups.sort_by(&:path).map do |group| + count = group.users.count + { username: group.path, name: "#{group.name} (#{count})" } + end end def all_members - [{ username: "all", name: "Project and Group Members" }] + count = @project.team.members.flatten.count + [{ username: "all", name: "All Project and Group Members (#{count})" }] end end end From 1c7947ace0e4f79f1c354a3d63b5f2caedbe1e35 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 25 Feb 2015 21:40:27 +0100 Subject: [PATCH 076/109] Add UTF-8 character to version_sorter test --- spec/helpers/application_helper_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 6abf5b9813..5a868ad609 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -192,12 +192,12 @@ describe ApplicationHelper do it 'sorts tags in a natural order' do # Stub repository.tag_names to make sure we get some valid testing data expect(@project.repository).to receive(:tag_names). - and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v1.0.9a', - 'v2.0-rc1', 'v2.0rc2']) + and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿', + 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2']) expect(options[1][1]). - to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0-rc1', 'v1.0.10', 'v1.0.9', - 'v1.0.9a']) + to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10', + 'v1.0.9', 'v1.0.9a']) end end From 4803af45dfbd516890b3ab31fa55b93009174d63 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 25 Feb 2015 18:22:08 +0100 Subject: [PATCH 077/109] Prevent another migration from failing. --- db/migrate/20141006143943_move_slack_service_to_webhook.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb index a8e07033a5..5836cd6b8d 100644 --- a/db/migrate/20141006143943_move_slack_service_to_webhook.rb +++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb @@ -10,7 +10,7 @@ class MoveSlackServiceToWebhook < ActiveRecord::Migration slack_service.properties.delete('subdomain') # Room is configured on the Slack side slack_service.properties.delete('room') - slack_service.save + slack_service.save(validate: false) end end end From 43f1ab9c12630e85861003c424da43314c2768a8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 25 Feb 2015 23:23:49 +0100 Subject: [PATCH 078/109] Move CHANGELOG entry to 7.9. --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dce52b7c70..05413e02a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 7.9.0 (unreleased) - Save web edit in new branch - Fix ordering of imported but unchanged projects (Marco Wessel) - Mobile UI improvements: make aside content expandable + - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) v 7.8.1 - Fix run of custom post receive hooks @@ -20,8 +21,6 @@ v 7.8.1 v 7.8.0 - Fix access control and protection against XSS for note attachments and other uploads. - - Fix broken access control for note attachments (Hannes Rosenögger) - - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) - Replace highlight.js with rouge-fork rugments (Stefan Tatschner) - Make project search case insensitive (Hannes Rosenögger) - Include issue/mr participants in list of recipients for reassign/close/reopen emails From 1511506f724d2ac33c2c8221035109961b36b28a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Feb 2015 15:17:20 -0800 Subject: [PATCH 079/109] Fix git syntax issue --- app/models/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 4e45a6723b..bbf35f04bb 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -238,7 +238,7 @@ class Repository end def last_commit_for_path(sha, path) - args = %W(git rev-list --max-count 1 #{sha} -- #{path}) + args = %W(git rev-list --max-count=1 #{sha} -- #{path}) sha = Gitlab::Popen.popen(args, path_to_repo).first.strip commit(sha) end From 1da71cc520dd09098d8f756de3f58b8e2f153fcd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Feb 2015 19:34:16 -0800 Subject: [PATCH 080/109] Introduce shortcuts for routing helpers --- app/controllers/projects/avatars_controller.rb | 2 +- .../projects/repositories_controller.rb | 2 +- app/controllers/projects_controller.rb | 8 ++++---- app/helpers/gitlab_routing_helper.rb | 18 ++++++++++++++++++ app/helpers/projects_helper.rb | 4 ++-- app/views/admin/projects/show.html.haml | 4 ++-- app/views/dashboard/_project.html.haml | 2 +- app/views/dashboard/projects.html.haml | 2 +- app/views/groups/_projects.html.haml | 2 +- app/views/layouts/nav/_project.html.haml | 6 +++--- app/views/projects/_settings_nav.html.haml | 2 +- app/views/projects/diffs/_warning.html.haml | 4 ++-- .../projects/issues/_discussion.html.haml | 4 ++-- app/views/projects/issues/_issue.html.haml | 8 ++++---- app/views/projects/issues/show.html.haml | 4 ++-- .../merge_requests/_discussion.html.haml | 4 ++-- .../projects/merge_requests/_show.html.haml | 10 +++++----- .../merge_requests/show/_mr_title.html.haml | 4 ++-- app/views/projects/milestones/_issue.html.haml | 2 +- .../milestones/_merge_request.html.haml | 2 +- app/views/projects/no_repo.html.haml | 2 +- 21 files changed, 57 insertions(+), 39 deletions(-) create mode 100644 app/helpers/gitlab_routing_helper.rb diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index b90a95c3aa..a482b90880 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -24,6 +24,6 @@ class Projects::AvatarsController < Projects::ApplicationController @project.save @project.reset_events_cache - redirect_to edit_namespace_project_path(@project.namespace, @project) + redirect_to edit_project_path(@project) end end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 245dfb7bb9..cbb888b25e 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -7,7 +7,7 @@ class Projects::RepositoriesController < Projects::ApplicationController def create @project.create_repository - redirect_to namespace_project_path(@project.namespace, @project) + redirect_to project_path(@project) end def archive diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 8a055cc2a3..5486a97e51 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -23,7 +23,7 @@ class ProjectsController < ApplicationController if @project.saved? redirect_to( - namespace_project_path(@project.namespace, @project), + project_path(@project), notice: 'Project was successfully created.' ) else @@ -39,7 +39,7 @@ class ProjectsController < ApplicationController flash[:notice] = 'Project was successfully updated.' format.html do redirect_to( - edit_namespace_project_path(@project.namespace, @project), + edit_project_path(@project), notice: 'Project was successfully updated.' ) end @@ -133,7 +133,7 @@ class ProjectsController < ApplicationController @project.archive! respond_to do |format| - format.html { redirect_to namespace_project_path(@project.namespace, @project) } + format.html { redirect_to project_path(@project) } end end @@ -142,7 +142,7 @@ class ProjectsController < ApplicationController @project.unarchive! respond_to do |format| - format.html { redirect_to namespace_project_path(@project.namespace, @project) } + format.html { redirect_to project_path(@project) } end end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb new file mode 100644 index 0000000000..932e0d2914 --- /dev/null +++ b/app/helpers/gitlab_routing_helper.rb @@ -0,0 +1,18 @@ +# Shorter routing method for project and project items +module GitlabRoutingHelper + def project_path(project, *args) + namespace_project_path(project.namespace, project, *args) + end + + def edit_project_path(project, *args) + edit_namespace_project_path(project.namespace, project, *args) + end + + def issue_path(entity, *args) + namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args) + end + + def merge_request_path(entity, *args) + namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args) + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index c85ad12634..a5d7372bbe 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -46,7 +46,7 @@ module ProjectsHelper simple_sanitize(project.group.name), group_path(project.group) ) + ' / ' + link_to(simple_sanitize(project.name), - namespace_project_path(project.namespace, project)) + project_path(project)) end else owner = project.namespace.owner @@ -55,7 +55,7 @@ module ProjectsHelper simple_sanitize(owner.name), user_path(owner) ) + ' / ' + link_to(simple_sanitize(project.name), - namespace_project_path(project.namespace, project)) + project_path(project)) end end end diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 3bcf1cc9ed..1421c2ea90 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,6 +1,6 @@ %h3.page-title Project: #{@project.name_with_namespace} - = link_to edit_namespace_project_path(@project.namespace, @project), class: "btn pull-right" do + = link_to edit_project_path(@project), class: "btn pull-right" do %i.fa.fa-pencil-square-o Edit %hr @@ -13,7 +13,7 @@ %li %span.light Name: %strong - = link_to @project.name, namespace_project_path(@project.namespace, @project) + = link_to @project.name, project_path(@project) %li %span.light Namespace: %strong diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml index 3dd69df523..fa9179cb24 100644 --- a/app/views/dashboard/_project.html.haml +++ b/app/views/dashboard/_project.html.haml @@ -1,4 +1,4 @@ -= link_to namespace_project_path(project.namespace, project), class: dom_class(project) do += link_to project_path(project), class: dom_class(project) do .dash-project-avatar = project_icon(project, alt: '', class: 'avatar project-avatar s40') .dash-project-access-icon diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index e57e1e0939..15db859254 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -19,7 +19,7 @@ = project_icon("#{project.namespace.to_param}/#{project.to_param}", alt: '', class: 'avatar project-avatar s60') .project-access-icon = visibility_level_icon(project.visibility_level) - = link_to namespace_project_path(project.namespace, project), class: dom_class(project) do + = link_to project_path(project), class: dom_class(project) do %strong= project.name_with_namespace - if project.forked_from_project diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 2f28470f8b..b505760fa8 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -11,7 +11,7 @@ .nothing-here-block This group has no projects yet - projects.each do |project| %li.project-row - = link_to namespace_project_path(project.namespace, project), class: dom_class(project) do + = link_to project_path(project), class: dom_class(project) do .dash-project-avatar = project_icon(project, alt: '', class: 'avatar s40') .dash-project-access-icon diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index ef31537b84..d340ab1796 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,7 +1,7 @@ %ul.project-navigation.nav.nav-sidebar - if @project_settings_nav = nav_link do - = link_to namespace_project_path(@project.namespace, @project), title: 'Back to project', class: "" do + = link_to project_path(@project), title: 'Back to project', class: "" do %i.fa.fa-caret-square-o-left %span Back to project @@ -12,7 +12,7 @@ - else = nav_link(path: 'projects#show', html_options: {class: "home"}) do - = link_to namespace_project_path(@project.namespace, @project), title: 'Project', class: 'shortcuts-project' do + = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do %i.fa.fa-dashboard %span Project @@ -89,7 +89,7 @@ - if project_nav_tab? :settings = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do - = link_to edit_namespace_project_path(@project.namespace, @project), title: 'Settings', class: "stat-tab tab no-highlight" do + = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do %i.fa.fa-cogs %span Settings diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml index 1a18bb065a..7fc3d44034 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -1,6 +1,6 @@ %ul.project-settings-nav.sidebar-subnav = nav_link(path: 'projects#edit') do - = link_to edit_namespace_project_path(@project.namespace, @project), title: 'Project', class: "stat-tab tab " do + = link_to edit_project_path(@project), title: 'Project', class: "stat-tab tab " do %i.fa.fa-pencil-square-o %span Project diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index 5725c84600..c9a6b3ebd9 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -10,8 +10,8 @@ = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-small" = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-warning btn-small" - elsif @merge_request && @merge_request.persisted? - = link_to "Plain diff", namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :diff), class: "btn btn-warning btn-small" - = link_to "Email patch", namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :patch), class: "btn btn-warning btn-small" + = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-warning btn-small" + = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-small" %p To preserve performance only %strong #{allowed_diff_size} of #{diffs.size} diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 2bd3d8a73e..fc3e35640d 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -1,9 +1,9 @@ - content_for :note_actions do - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen Issue', namespace_project_issue_path(@project.namespace, @project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue' + = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue' - else - = link_to 'Close Issue', namespace_project_issue_path(@project.namespace, @project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" + = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" .row %section.col-md-9 .participants diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 8af8da1d13..01e2133e28 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,11 +1,11 @@ -%li{ id: dom_id(issue), class: issue_css_classes(issue), url: namespace_project_issue_path(issue.project.namespace, issue.project, issue) } +%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) } - if controller.controller_name == 'issues' .issue-check = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) .issue-title %span.str-truncated - = link_to_gfm issue.title, namespace_project_issue_path(issue.project.namespace, issue.project, issue), class: "row_title" + = link_to_gfm issue.title, issue_path(issue), class: "row_title" .pull-right.light - if issue.closed? %span @@ -41,9 +41,9 @@ .issue-actions - if can? current_user, :modify_issue, issue - if issue.closed? - = link_to 'Reopen', namespace_project_issue_path(issue.project.namespace, issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue btn-reopen", remote: true + = link_to 'Reopen', issue_path(issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue btn-reopen", remote: true - else - = link_to 'Close', namespace_project_issue_path(issue.project.namespace, issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true + = link_to 'Close', issue_path(issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true = link_to edit_namespace_project_issue_path(issue.project.namespace, issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do %i.fa.fa-pencil-square-o Edit diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 6849a15e7e..bd28d8a1db 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -17,9 +17,9 @@ New Issue - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen', namespace_project_issue_path(@project.namespace, @project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" + = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" - else - = link_to 'Close', namespace_project_issue_path(@project.namespace, @project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" + = link_to 'Close', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: "btn btn-grouped issuable-edit" do %i.fa.fa-pencil-square-o diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 2df35aac02..79a093dc77 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -1,9 +1,9 @@ - content_for :note_actions do - if can?(current_user, :modify_merge_request, @merge_request) - if @merge_request.open? - = link_to 'Close', namespace_project_merge_request_path(@project.namespace, @project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" + = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" - if @merge_request.closed? - = link_to 'Reopen', namespace_project_merge_request_path(@project.namespace, @project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" .row %section.col-md-9 diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index a53aed2f38..ca4ceecb22 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,4 @@ -.merge-request{'data-url' => namespace_project_merge_request_path(@project.namespace, @project, @merge_request)} +.merge-request{'data-url' => merge_request_path(@merge_request)} .merge-request-details = render "projects/merge_requests/show/mr_title" %hr @@ -28,8 +28,8 @@ Download as %span.caret %ul.dropdown-menu - %li= link_to "Email Patches", namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :patch) - %li= link_to "Plain Diff", namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :diff) + %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) + %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/show/state_widget" @@ -37,12 +37,12 @@ - if @commits.present? %ul.nav.nav-tabs.merge-request-tabs %li.notes-tab{data: {action: 'notes'}} - = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request) do + = link_to merge_request_path(@merge_request) do %i.fa.fa-comments Discussion %span.badge= @merge_request.mr_and_commit_notes.count %li.commits-tab{data: {action: 'commits'}} - = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), title: 'Commits' do + = link_to merge_request_path(@merge_request), title: 'Commits' do %i.fa.fa-history Commits %span.badge= @commits.size diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 4c230953cb..46e92a9c55 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -14,9 +14,9 @@ .issue-btn-group.pull-right - if can?(current_user, :modify_merge_request, @merge_request) - if @merge_request.open? - = link_to 'Close', namespace_project_merge_request_path(@project.namespace, @project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do %i.fa.fa-pencil-square-o Edit - if @merge_request.closed? - = link_to 'Reopen', namespace_project_merge_request_path(@project.namespace, @project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml index 36463371f4..26c83841a2 100644 --- a/app/views/projects/milestones/_issue.html.haml +++ b/app/views/projects/milestones/_issue.html.haml @@ -1,4 +1,4 @@ -%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => namespace_project_issue_path(@project.namespace, @project, issue) } +%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) } %span.str-truncated = link_to [@project.namespace.becomes(Namespace), @project, issue] do %span.cgray ##{issue.iid} diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index 3180c1d91b..46f2df1b18 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -1,4 +1,4 @@ -%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => namespace_project_merge_request_path(@project.namespace, @project, merge_request) } +%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) } %span.str-truncated = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do %span.cgray ##{merge_request.iid} diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml index e8fd90efd1..720957e833 100644 --- a/app/views/projects/no_repo.html.haml +++ b/app/views/projects/no_repo.html.haml @@ -19,4 +19,4 @@ - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to 'Remove project', namespace_project_path(@project.namespace, @project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" + = link_to 'Remove project', project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" From 0a4dec24c8effab297c195301f1213ab09d94633 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Feb 2015 19:41:17 -0800 Subject: [PATCH 081/109] Add explanation to routing method --- app/helpers/gitlab_routing_helper.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 932e0d2914..f0eb50a0e1 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -1,4 +1,17 @@ # Shorter routing method for project and project items +# Since update to rails 4.1.9 we are now allowed to use `/` in project routing +# so we use nested routing for project resources which include project and +# project namespace. To avoid writing long methods every time we define shortcuts for +# some of routing. +# +# For example instead of this: +# +# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request) +# +# We can simply use shortcut: +# +# merge_request_path(merge_request) +# module GitlabRoutingHelper def project_path(project, *args) namespace_project_path(project.namespace, project, *args) From 128012dba8737b0dc65d41a3eb1690c9d8797a34 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Feb 2015 19:50:01 -0800 Subject: [PATCH 082/109] More use of shortcut routes --- app/controllers/projects/issues_controller.rb | 8 +++----- app/controllers/projects/merge_requests_controller.rb | 4 +--- .../projects/merge_requests/_merge_request.html.haml | 2 +- app/views/projects/merge_requests/show/_diffs.html.haml | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 73b58285c6..6a2af08a19 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -60,8 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController respond_to do |format| format.html do if @issue.valid? - redirect_to namespace_project_issue_path(@project.namespace, - @project, @issue) + redirect_to issue_path(@issue) else render :new end @@ -79,7 +78,7 @@ class Projects::IssuesController < Projects::ApplicationController format.js format.html do if @issue.valid? - redirect_to [@project.namespace.becomes(Namespace), @project, @issue] + redirect_to issue_path(@issue) else render :edit end @@ -129,8 +128,7 @@ class Projects::IssuesController < Projects::ApplicationController issue = @project.issues.find_by(id: params[:id]) if issue - redirect_to namespace_project_issue_path(@project.namespace, @project, - issue) + redirect_to issue_path(issue) return else raise ActiveRecord::RecordNotFound.new diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 98e4775e40..f07923d6d9 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -79,9 +79,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController if @merge_request.valid? redirect_to( - namespace_project_merge_request_path(@merge_request.target_project.namespace, - @merge_request.target_project, - @merge_request), + merge_request_path(@merge_request) notice: 'Merge request was successfully created.' ) else diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index d94636712b..1eba1a96b7 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -1,7 +1,7 @@ %li{ class: mr_css_classes(merge_request) } .merge-request-title %span.str-truncated - = link_to_gfm merge_request.title, namespace_project_merge_request_path(merge_request.target_project.namespace, merge_request.target_project, merge_request), class: "row_title" + = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" .pull-right.light - if merge_request.merged? %span diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index eb1640891e..cfef1d5e4c 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -8,5 +8,5 @@ Changes view for this comparison is extremely large. %p You can - = link_to "download it", namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, format: :diff), class: "vlink" + = link_to "download it", merge_request_path(@merge_request, format: :diff), class: "vlink" instead. From a9eba1bde0e0ccd86bbc1df32d2538f986105d55 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Feb 2015 19:51:13 -0800 Subject: [PATCH 083/109] No need to block db:rollback for safe migration --- db/migrate/20150223022001_set_missing_last_activity_at.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/migrate/20150223022001_set_missing_last_activity_at.rb b/db/migrate/20150223022001_set_missing_last_activity_at.rb index 3a3adf1887..3f6d4d8347 100644 --- a/db/migrate/20150223022001_set_missing_last_activity_at.rb +++ b/db/migrate/20150223022001_set_missing_last_activity_at.rb @@ -4,6 +4,5 @@ class SetMissingLastActivityAt < ActiveRecord::Migration end def down - raise ActiveRecord::IrreversibleMigration end end From e993b59b7dcaf795abb82af3f548f35aff01c6a8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Feb 2015 19:53:45 -0800 Subject: [PATCH 084/109] Dont render project entity --- app/views/projects/deploy_keys/_deploy_key.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index 52da85cbdf..230e164f24 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -13,7 +13,7 @@ = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right" - = key_project = deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first + - key_project = deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first = link_to namespace_project_deploy_key_path(key_project.namespace, key_project, deploy_key) do %i.fa.fa-key %strong= deploy_key.title From c254cb03d8ddfb217341c2f83223ba30228f3088 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Feb 2015 20:00:28 -0800 Subject: [PATCH 085/109] Fix affix for issue and merge request with image in description --- app/assets/javascripts/issue.js.coffee | 11 ++++++----- app/assets/javascripts/merge_request.js.coffee | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 9b7c1be835..f275317047 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -16,8 +16,9 @@ class @Issue updateTaskState ) - $('.issuable-affix').affix offset: - top: -> - @top = $('.issue-details').outerHeight(true) + 25 - bottom: -> - @bottom = $('.footer').outerHeight(true) + $('.issue-details').waitForImages -> + $('.issuable-affix').affix offset: + top: -> + @top = $('.issue-details').outerHeight(true) + 25 + bottom: -> + @bottom = $('.footer').outerHeight(true) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 757592842e..ec68631543 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -20,11 +20,12 @@ class @MergeRequest if $("a.btn-close").length $("li.task-list-item input:checkbox").prop("disabled", false) - $('.issuable-affix').affix offset: - top: -> - @top = $('.merge-request-details').outerHeight(true) + 70 - bottom: -> - @bottom = $('.footer').outerHeight(true) + $('.merge-request-details').waitForImages -> + $('.issuable-affix').affix offset: + top: -> + @top = $('.merge-request-details').outerHeight(true) + 91 + bottom: -> + @bottom = $('.footer').outerHeight(true) # Local jQuery finder $: (selector) -> From 8490a8ab2a70828e4bb5587c7cc07c750483ef2e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 25 Feb 2015 04:20:17 -0500 Subject: [PATCH 086/109] Rename bulk_update_context_spec to bulk_update_service_spec --- .../{bulk_update_context_spec.rb => bulk_update_service_spec.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/services/issues/{bulk_update_context_spec.rb => bulk_update_service_spec.rb} (100%) diff --git a/spec/services/issues/bulk_update_context_spec.rb b/spec/services/issues/bulk_update_service_spec.rb similarity index 100% rename from spec/services/issues/bulk_update_context_spec.rb rename to spec/services/issues/bulk_update_service_spec.rb From e53dd7526f69545ca86fc6935ad8077592628772 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 25 Feb 2015 04:29:33 -0500 Subject: [PATCH 087/109] Allow mass-unassigning of issues Fixes #867 [ci skip] --- CHANGELOG | 1 + .../project_users_select.js.coffee | 2 +- .../issues/bulk_update_service_spec.rb | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d5b0512511..3f9af5b9f9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 7.9.0 (unreleased) - Fix ordering of imported but unchanged projects (Marco Wessel) - Mobile UI improvements: make aside content expandable - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) + - Fix mass-unassignment of issues (Robert Speicher) v 7.8.1 - Fix run of custom post receive hooks diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee index 7fb3392609..885f0d58a6 100644 --- a/app/assets/javascripts/project_users_select.js.coffee +++ b/app/assets/javascripts/project_users_select.js.coffee @@ -15,7 +15,7 @@ class @ProjectUsersSelect name: 'Unassigned', avatar: null, username: 'none', - id: '' + id: -1 } data.results.unshift(nullUser) diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb index eb867f78c5..504213e667 100644 --- a/spec/services/issues/bulk_update_service_spec.rb +++ b/spec/services/issues/bulk_update_service_spec.rb @@ -84,6 +84,25 @@ describe Issues::BulkUpdateService do expect(@project.issues.first.assignee).to eq(@new_assignee) } + it 'allows mass-unassigning' do + @project.issues.first.update_attribute(:assignee, @new_assignee) + expect(@project.issues.first.assignee).not_to be_nil + + @params[:update][:assignee_id] = -1 + + Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(@project.issues.first.assignee).to be_nil + end + + it 'does not unassign when assignee_id is not present' do + @project.issues.first.update_attribute(:assignee, @new_assignee) + expect(@project.issues.first.assignee).not_to be_nil + + @params[:update][:assignee_id] = '' + + Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(@project.issues.first.assignee).not_to be_nil + end end describe :update_milestone do From e27f5aef462e5cf32f23fbb3137b98011c0c0ddf Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Feb 2015 22:17:46 -0800 Subject: [PATCH 088/109] Fix sticky diff header --- app/assets/javascripts/diff.js.coffee | 3 ++- app/assets/javascripts/merge_request.js.coffee | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee index b0b312e774..05f5af4257 100644 --- a/app/assets/javascripts/diff.js.coffee +++ b/app/assets/javascripts/diff.js.coffee @@ -1,6 +1,7 @@ class @Diff UNFOLD_COUNT = 20 constructor: -> + $(document).off('click', '.js-unfold') $(document).on('click', '.js-unfold', (event) => target = $(event.target) unfoldBottom = target.hasClass('js-unfold-bottom') @@ -36,7 +37,7 @@ class @Diff ) ) - $('.diff-header').stick_in_parent(offset_top: $('.navbar').height()) + $('.diff-header').stick_in_parent(recalc_every: 1, offset_top: $('.navbar').height()) lineNumbers: (line) -> return ([0, 0]) unless line.children().length diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 757592842e..805bf0203c 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -95,6 +95,7 @@ class @MergeRequest this.$('.merge-request-tabs .diffs-tab').addClass 'active' this.loadDiff() unless @diffs_loaded this.$('.diffs').show() + $(".diff-header").trigger("sticky_kit:recalc") when 'commits' this.$('.merge-request-tabs .commits-tab').addClass 'active' this.$('.commits').show() From d3c44b1a6c470990cd79d2e4feec3d1d9b450496 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 25 Feb 2015 10:59:50 +0100 Subject: [PATCH 089/109] Fix import status page project links for new Rails. --- app/views/import/base/create.js.haml | 2 +- app/views/import/bitbucket/status.html.haml | 2 +- app/views/import/github/status.html.haml | 2 +- app/views/import/gitlab/status.html.haml | 2 +- app/views/import/gitorious/status.html.haml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index 8ebdf4f1a2..8d10722628 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -20,6 +20,6 @@ job.attr("id", "project_#{@project.id}") target_field = job.find(".import-target") target_field.empty() - target_field.append('#{link_to @project.path_with_namespace, @project}') + target_field.append('#{link_to @project.path_with_namespace, [@project.namespace.becomes(Namespace), @project]}') $("table.import-jobs tbody").prepend(job) job.addClass("active").find(".import-actions").html(" started") diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 90c97393b5..bcbbaadf3e 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -20,7 +20,7 @@ %td = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, project + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span.cgreen diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 957022f382..883090a302 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -20,7 +20,7 @@ %td = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, project + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span.cgreen diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index db16168120..41ac073eae 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -20,7 +20,7 @@ %td = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, project + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span.cgreen diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index e06e068fdb..ebe24747a0 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -20,7 +20,7 @@ %td = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, project + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span.cgreen From 449cf43a55a34553fae591db7d69d1505b4daa53 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Thu, 26 Feb 2015 09:03:34 -0800 Subject: [PATCH 090/109] Add z index for diff header so sticky header stays on top on diff comments. --- app/assets/stylesheets/sections/diff.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss index f47ea32982..54311a6885 100644 --- a/app/assets/stylesheets/sections/diff.scss +++ b/app/assets/stylesheets/sections/diff.scss @@ -8,6 +8,7 @@ border-bottom: 1px solid #CCC; padding: 5px 5px 5px 10px; color: #555; + z-index: 10; > span { font-family: $monospace_font; From 4efe3cf5569045c3f115777a448c042ed3ba1d22 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 26 Feb 2015 18:25:59 +0100 Subject: [PATCH 091/109] More reasons why prefixing is good Inspired by http://www.dwheeler.com/essays/filenames-in-shell.html --- doc/development/shell_commands.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md index 42f17e1953..821027f43f 100644 --- a/doc/development/shell_commands.md +++ b/doc/development/shell_commands.md @@ -139,6 +139,11 @@ path = File.join(repo_path, user_input) File.read(path) ``` +If you have to use user input a relative path, prefix `./` to the path. + +Prefixing user-supplied paths also offers extra protection against paths +starting with `-` (see the discussion about using `--` above). + ## Guard against path traversal Path traversal is a security where the program (GitLab) tries to restrict user From 6de4e4a622a98d86a44e9adf2fca15ff30c478c7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 26 Feb 2015 09:34:20 -0800 Subject: [PATCH 092/109] Include route helper shortcut in controller --- app/controllers/application_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7940b5cb3f..df1a588313 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,7 @@ require 'gon' class ApplicationController < ActionController::Base include Gitlab::CurrentSettings + include GitlabRoutingHelper before_filter :authenticate_user_from_token! before_filter :authenticate_user! From 62a81494ba22a33e5c798d9ce169a64239166f34 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Thu, 26 Feb 2015 16:03:42 -0800 Subject: [PATCH 093/109] Update project_milestone_path update route. --- app/controllers/projects/milestones_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 97eaabb15c..afdb560e73 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -54,7 +54,8 @@ class Projects::MilestonesController < Projects::ApplicationController format.js format.html do if @milestone.valid? - redirect_to [@project, @milestone] + redirect_to namespace_project_milestone_path(@project.namespace, + @project, @milestone) else render :edit end From 6ac0a0217cfcfa9915d5380337b9e5dd25b699ea Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 26 Feb 2015 16:44:52 -0800 Subject: [PATCH 094/109] Fix syntax issue --- app/controllers/projects/merge_requests_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index f07923d6d9..26d4c51773 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -79,7 +79,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController if @merge_request.valid? redirect_to( - merge_request_path(@merge_request) + merge_request_path(@merge_request), notice: 'Merge request was successfully created.' ) else From 804a2488cfd6704726c0dc7e6b0facc7e2ff7ce3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 27 Feb 2015 10:47:37 +0100 Subject: [PATCH 095/109] Fix and test User#contributed_projects_ids. --- app/models/user.rb | 1 + spec/models/user_spec.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 27ac93f484..55768a351e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -629,5 +629,6 @@ class User < ActiveRecord::Base reorder(project_id: :desc). select(:project_id). uniq + .map(&:project_id) end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c015a1d268..29d0c24e87 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -505,4 +505,32 @@ describe User do expect(User.sort(nil).first).to eq(@user) end end + + describe "#contributed_projects_ids" do + + subject { create(:user) } + let!(:project1) { create(:project) } + let!(:project2) { create(:project, forked_from_project: project3) } + let!(:project3) { create(:project) } + let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) } + let!(:push_event) { create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject) } + let!(:merge_event) { create(:event, action: Event::CREATED, project: project3, target: merge_request, author: subject) } + + before do + project1.team << [subject, :master] + project2.team << [subject, :master] + end + + it "includes IDs for projects the user has pushed to" do + expect(subject.contributed_projects_ids).to include(project1.id) + end + + it "includes IDs for projects the user has had merge requests merged into" do + expect(subject.contributed_projects_ids).to include(project3.id) + end + + it "doesn't include IDs for unrelated projects" do + expect(subject.contributed_projects_ids).not_to include(project2.id) + end + end end From 7202db072f802a7f003684b300995251dd518376 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 27 Feb 2015 11:26:47 +0100 Subject: [PATCH 096/109] Redirect old note attachment path to new uploads path. --- config/routes.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 35053bdb20..e152b27a26 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -97,6 +97,10 @@ Gitlab::Application.routes.draw do constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /.+/ } end + get "files/note/:id/:filename", + to: redirect("uploads/note/attachment/%{id}/%{filename}"), + constraints: { filename: /.+/ } + # # Explore area # From 3e6b342a1ab6702d2f897ecc0f259d3876e65ade Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 27 Feb 2015 10:38:15 -0800 Subject: [PATCH 097/109] Remove CI sections related to release tool, update steps in monthly and patch release documentation. --- doc/release/howto_rc1.md | 25 +++---------------------- doc/release/monthly.md | 34 +++++++++++++++------------------- doc/release/patch.md | 2 +- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/doc/release/howto_rc1.md b/doc/release/howto_rc1.md index c4156d25d5..07c703142d 100644 --- a/doc/release/howto_rc1.md +++ b/doc/release/howto_rc1.md @@ -27,7 +27,7 @@ Make sure the code quality indicators are green / good. - [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) -### 4. Run release tool for CE and EE +### 4. Run release tool **Make sure EE `master` has latest changes from CE `master`** @@ -38,8 +38,8 @@ git clone git@dev.gitlab.org:gitlab/release-tools.git cd release-tools ``` -Release candidate creates stable branch from master. -So we need to sync master branch between all CE remotes. Also do same for EE. +Release candidate creates stable branch from master. +So we need to sync master branch between all CE, EE and CI remotes. ``` bundle exec rake sync @@ -53,22 +53,3 @@ bundle exec rake release["x.x.0.rc1"] Now developers can use master for merging new features. So you should use stable branch for future code changes related to release. - - -### 5. Release GitLab CI RC1 - -Add to your local `gitlab-ci/.git/config`: - -``` -[remote "public"] - url = none - pushurl = git@dev.gitlab.org:gitlab/gitlab-ci.git - pushurl = git@gitlab.com:gitlab-org/gitlab-ci.git - pushurl = git@github.com:gitlabhq/gitlab-ci.git -``` - -* Create a stable branch `x-y-stable` -* Bump VERSION to `x.y.0.rc1` -* `git tag -a v$(cat VERSION) -m "Version $(cat VERSION)"` -* `git push public x-y-stable v$(cat VERSION)` - diff --git a/doc/release/monthly.md b/doc/release/monthly.md index c9e6d3426b..dd44c1eb86 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -15,7 +15,7 @@ This person should also make sure this document is kept up to date and issues ar ## Take vacations into account -The time is measured in weekdays to compensate for weekends. +The time is measured in weekdays to compensate for weekends. Do everything on time to prevent problems due to rush jobs or too little testing time. Make sure that you take into account any vacations of maintainers. If the release is falling behind immediately warn the team. @@ -38,29 +38,30 @@ Xth: (7 working days before the 22nd) Xth: (6 working days before the 22nd) - [ ] Merge CE master in to EE master via merge request (#LINK) -- [ ] Create CE, EE, CI RC1 versions (#LINK) - [ ] Determine QA person and notify this person +- [ ] Check the tasks in [how to rc1 guide](howto_rc1.md) and delegate tasks if necessary +- [ ] Create CE, EE, CI RC1 versions (#LINK) Xth: (5 working days before the 22nd) - [ ] Do QA and fix anything coming out of it (#LINK) - [ ] Close the omnibus-gitlab milestone +- [ ] Prepare the blog post (#LINK) Xth: (4 working days before the 22nd) -- [ ] Build rc1 package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) - [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) +- [ ] Create regression issues (CE, CI) (#LINK) +- [ ] Tweet about rc1 (#LINK) Xth: (3 working days before the 22nd) -- [ ] Create regression issues (CE, CI) (#LINK) -- [ ] Tweet about rc1 (#LINK) -- [ ] Prepare the blog post (#LINK) +- [ ] Merge CE stable branch into EE stable branch Xth: (2 working days before the 22nd) -- [ ] Merge CE stable branch into EE stable branch - [ ] Check that everyone is mentioned on the blog post (the reviewer should have done this one working day ago) +- [ ] Check that MVP is added to the mvp page (source/mvp/index.html in www-gitlab-com) Xth: (1 working day before the 22nd) @@ -93,13 +94,13 @@ There are three changelogs that need to be updated: CE, EE and CI. ## Prepare CHANGELOG for next release -Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version. +Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version, usually X.X.X.pre. ## QA Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X QA" in order to keep track of the progress. -Use the omnibus packages of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md). +Use the omnibus packages created for RC1 of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md). **NOTE** Upgrader can only be tested when tags are pushed to all repositories. Do not forget to confirm it is working before releasing. Note that in the issue. @@ -112,8 +113,7 @@ create an issue about it in order to discuss the next steps after the release. ## Update GitLab.com with RC1 -Merge the RC1 EE code into GitLab.com. -Once the build is green, create a package. +Use the omnibus EE packages created for RC1. If there are big database migrations consider testing them with the production db on a VM. Try to deploy in the morning. It is important to do this as soon as possible, so we can catch any errors before we release the full version. @@ -127,7 +127,7 @@ Please do not raise issues directly in this issue but link to issues that might The decision to create a patch release or not is with the release manager who is assigned to this issue. The release manager will comment here about the plans for patch releases. -Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately. +Assign the issue to the release manager and at mention all members of gitlab core team. If there are any known bugs in the release add them immediately. ## Tweet about RC1 @@ -143,8 +143,8 @@ Tweet about the RC release: 1. Also check the CI changelog 1. Add a proposed tweet text to the blog post WIP MR description. 1. Create a WIP MR for the blog post -1. Ask Dmitriy to add screenshots to the WIP MR. -1. Decide with team who will be the MVP user. +1. Ask Dmitriy (or a team member with OS X) to add screenshots to the WIP MR. +1. Decide with core team who will be the MVP user. 1. Create WIP MR for adding MVP to MVP page on website 1. Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. 1. Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master) @@ -166,11 +166,7 @@ Bump version, create release tag and push to remotes: bundle exec rake release["x.x.0"] ``` -Also perform these steps for GitLab CI: - -1. bump version in the stable branch -1. create annotated tag -1. push the stable branch and the annotated tag to the public repositories +This will create correct version and tag and push to all CE, EE and CI remotes. Update [installation.md](/doc/install/installation.md) to the newest version in master. diff --git a/doc/release/patch.md b/doc/release/patch.md index d8bb4aef0e..80afa19b6c 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -51,6 +51,6 @@ CE=false be rake release['x.x.x'] 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) 1. Apply the patch to GitLab.com and the private GitLab development server -1. Create and publish a blog post +1. Create and publish a blog post, see [patch release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/patch_release_blog_template.md) 1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) From 238455aa97c21cf4126994627725ba0196decf9c Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 27 Feb 2015 12:32:05 -0800 Subject: [PATCH 098/109] Mention audit events ee feature in logs documentation. --- doc/logs/logs.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/logs/logs.md b/doc/logs/logs.md index 07302894dd..ec0109a426 100644 --- a/doc/logs/logs.md +++ b/doc/logs/logs.md @@ -1,6 +1,8 @@ ## Log system GitLab has advanced log system so everything is logging and you can analize your instance using various system log files. -These log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files. +In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://doc.gitlab.com/ee/administration/audit_events.html) + +System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files. #### production.log This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/logs/production.log` for installations from the source. @@ -12,7 +14,7 @@ This task is more useful for GitLab contributors and developers. Use part of thi Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200 Processing by Projects::TreeController#show as HTML Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"} - + ... [CUT OUT] amespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1 [["id", 26]] From 9e4d3f328f00459447e087677a1684e54faa47c8 Mon Sep 17 00:00:00 2001 From: Mlanawo Mbechezi Date: Fri, 27 Feb 2015 22:49:40 +0100 Subject: [PATCH 099/109] fix typo --- app/views/admin/services/index.html.haml | 2 +- app/views/projects/services/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml index 1d3e192a32..0093fb9776 100644 --- a/app/views/admin/services/index.html.haml +++ b/app/views/admin/services/index.html.haml @@ -6,7 +6,7 @@ %tr %th %th Service - %th Desription + %th Description %th Last edit - @services.sort_by(&:title).each do |service| %tr diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml index d615d12865..0d3ccb6bb8 100644 --- a/app/views/projects/services/index.html.haml +++ b/app/views/projects/services/index.html.haml @@ -6,7 +6,7 @@ %tr %th %th Service - %th Desription + %th Description %th Last edit - @services.sort_by(&:title).each do |service| %tr From df2353716b076a445c454dcb2da1f09e06f7f3c5 Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Fri, 27 Feb 2015 16:22:53 -0800 Subject: [PATCH 100/109] Changed header to Create New Issue --- app/views/projects/issues/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 76075124f9..4da42c83db 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,5 +1,5 @@ %div.issue-form-holder - %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" + %h3.page-title= @issue.new_record? ? "Create New Issue" : "Edit Issue ##{@issue.iid}" %hr = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f| From a41b9533fe569e1cd8471fbac8a34447560ecbb8 Mon Sep 17 00:00:00 2001 From: Sabba Petri Date: Fri, 27 Feb 2015 16:24:05 -0800 Subject: [PATCH 101/109] Changed to "Create Issue" --- app/views/projects/issues/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 4da42c83db..7d7217eb2a 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,5 +1,5 @@ %div.issue-form-holder - %h3.page-title= @issue.new_record? ? "Create New Issue" : "Edit Issue ##{@issue.iid}" + %h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}" %hr = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f| From d2c85a68bb763d2beccf8ebc0087791f6714c6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A8s=20Koetsier?= Date: Sun, 28 Dec 2014 21:08:33 +0100 Subject: [PATCH 102/109] Allow a user to specify a channel and username for the slack-webhook --- CHANGELOG | 1 + .../projects/services_controller.rb | 2 +- app/models/project_services/slack_service.rb | 13 ++++++++--- .../project_services/slack_service_spec.rb | 22 +++++++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6803855fcf..58ae481109 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -88,6 +88,7 @@ v 7.8.0 - Improve database performance for GitLab - Add Asana service (Jeremy Benoist) - Improve project web hooks with extra data + - Slack username and channel options v 7.7.2 - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 5c29a6550f..ce0f98bdef 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -50,7 +50,7 @@ class Projects::ServicesController < Projects::ApplicationController :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :build_key, :server, :teamcity_url, :build_type, - :description, :issues_url, :new_issue_url, :restrict_to_branch + :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel ) end end diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 297d8bbb5d..c7cbff63fe 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -14,7 +14,7 @@ # class SlackService < Service - prop_accessor :webhook + prop_accessor :webhook, :username, :channel validates :webhook, presence: true, if: :activated? def title @@ -31,7 +31,10 @@ class SlackService < Service def fields [ - { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' } + { type: 'text', name: 'webhook', + placeholder: 'https://hooks.slack.com/services/...' }, + { type: 'text', name: 'username', placeholder: 'username' }, + { type: 'text', name: 'channel', placeholder: '#channel' } ] end @@ -43,7 +46,11 @@ class SlackService < Service project_name: project_name )) - notifier = Slack::Notifier.new(webhook) + opt = {} + opt[:channel] = channel if channel + opt[:username] = username if username + + notifier = Slack::Notifier.new(webhook, opt) notifier.ping(message.pretext, attachments: message.attachments) end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 90b385423f..8a75d8987a 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -36,6 +36,8 @@ describe SlackService do let(:project) { create(:project) } let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } + let(:username) { 'slack_username' } + let(:channel) { 'slack_channel' } before do slack.stub( @@ -53,5 +55,25 @@ describe SlackService do expect(WebMock).to have_requested(:post, webhook_url).once end + + it 'should use the username as an option for slack when configured' do + slack.stub(username: username) + expect(Slack::Notifier).to receive(:new). + with(webhook_url, username: username). + and_return( + double(:slack_service).as_null_object + ) + slack.execute(sample_data) + end + + it 'should use the channel as an option when it is configured' do + slack.stub(channel: channel) + expect(Slack::Notifier).to receive(:new). + with(webhook_url, channel: channel). + and_return( + double(:slack_service).as_null_object + ) + slack.execute(sample_data) + end end end From ff696856079c984332de167219f2768415e6730f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 27 Feb 2015 11:26:47 +0100 Subject: [PATCH 103/109] Add comment about note attachment redirect. --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index e152b27a26..6329917693 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -97,6 +97,7 @@ Gitlab::Application.routes.draw do constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /.+/ } end + # Redirect old note attachments path to new uploads path. get "files/note/:id/:filename", to: redirect("uploads/note/attachment/%{id}/%{filename}"), constraints: { filename: /.+/ } From 51abeaa1bc93862a4d15506a590704f9fc56cfd6 Mon Sep 17 00:00:00 2001 From: sue445 Date: Sun, 1 Mar 2015 02:07:53 +0900 Subject: [PATCH 104/109] Expose avatar_url in projects API * Impl Project#avatar_url * Refactor ApplicationHelper: Use Project#avatar_url * Update changelog --- CHANGELOG | 1 + app/helpers/application_helper.rb | 6 ++--- app/models/project.rb | 10 ++++++++ doc/api/projects.md | 9 ++++--- lib/api/entities.rb | 1 + spec/helpers/application_helper_spec.rb | 6 +++-- spec/models/project_spec.rb | 31 +++++++++++++++++++++++++ 7 files changed, 55 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d5b0512511..f534f50b7a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 7.9.0 (unreleased) - Save web edit in new branch - Fix ordering of imported but unchanged projects (Marco Wessel) - Mobile UI improvements: make aside content expandable + - Expose avatar_url in projects API - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) v 7.8.1 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 365de3595c..a81e41819b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -58,10 +58,8 @@ module ApplicationHelper Project.find_with_namespace(project_id) end - if project.avatar.present? - image_tag project.avatar.url, options - elsif project.avatar_in_git - image_tag namespace_project_avatar_path(project.namespace, project), options + if project.avatar_url + image_tag project.avatar_url, options else # generated icon project_identicon(project, options) end diff --git a/app/models/project.rb b/app/models/project.rb index d33b25db20..7f2e0b4c17 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -37,6 +37,8 @@ class Project < ActiveRecord::Base include Gitlab::ShellAdapter include Gitlab::VisibilityLevel include Gitlab::ConfigHelper + include Rails.application.routes.url_helpers + extend Gitlab::ConfigHelper extend Enumerize @@ -408,6 +410,14 @@ class Project < ActiveRecord::Base @avatar_file end + def avatar_url + if avatar.present? + [gitlab_config.url, avatar.url].join + elsif avatar_in_git + [gitlab_config.url, namespace_project_avatar_path(namespace, self)].join + end + end + # For compatibility with old code def code path diff --git a/doc/api/projects.md b/doc/api/projects.md index a1a23051d7..7fe244477d 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -68,7 +68,8 @@ Parameters: "path": "diaspora", "updated_at": "2013-09-30T13: 46: 02Z" }, - "archived": false + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png" }, { "id": 6, @@ -103,7 +104,8 @@ Parameters: "path": "brightbox", "updated_at": "2013-09-30T13:46:02Z" }, - "archived": false + "archived": false, + "avatar_url": null } ] ``` @@ -195,7 +197,8 @@ Parameters: "notification_level": 3 } }, - "archived": false + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png" } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 7572104fc1..af76f3c439 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -56,6 +56,7 @@ module API expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at expose :namespace expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } + expose :avatar_url end class ProjectMember < UserBasic diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 9d99b6e33c..de491ce8a5 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -64,8 +64,9 @@ describe ApplicationHelper do project = create(:project) project.avatar = File.open(avatar_file_path) project.save! + avatar_url = "http://localhost/uploads/project/avatar/#{ project.id }/gitlab_logo.png" expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to eq( - "\"Gitlab" + "\"Gitlab" ) end @@ -75,8 +76,9 @@ describe ApplicationHelper do allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) + avatar_url = 'http://localhost' + namespace_project_avatar_path(project.namespace, project) expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match( - image_tag(namespace_project_avatar_path(project.namespace, project))) + image_tag(avatar_url)) end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a9df6f137b..879a63dd9f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -326,4 +326,35 @@ describe Project do expect(project.avatar_type).to eq(['only images allowed']) end end + + describe :avatar_url do + subject { project.avatar_url } + + let(:project) { create(:project) } + + context 'When avatar file is uploaded' do + before do + project.update_columns(avatar: 'uploads/avatar.png') + allow(project.avatar).to receive(:present?) { true } + end + + let(:avatar_path) do + "/uploads/project/avatar/#{project.id}/uploads/avatar.png" + end + + it { should eq "http://localhost#{avatar_path}" } + end + + context 'When avatar file in git' do + before do + allow(project).to receive(:avatar_in_git) { true } + end + + let(:avatar_path) do + "/#{project.namespace.name}/#{project.path}/avatar" + end + + it { should eq "http://localhost#{avatar_path}" } + end + end end From 7486bc0ae33adc141abbca2ea5dea833e56d5409 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sun, 1 Mar 2015 09:40:04 +0100 Subject: [PATCH 105/109] Update Dockerfile for GitLab 7.8.1 --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3a0a55e18e..3584a754c6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update -q \ # If the Omnibus package version below is outdated please contribute a merge request to update it. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it. RUN TMP_FILE=$(mktemp); \ - wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.0-omnibus-1_amd64.deb \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.1-omnibus-1_amd64.deb \ && dpkg -i $TMP_FILE \ && rm -f $TMP_FILE From f84b7eef3f969a65d0930c9d62b6968b2ae70f12 Mon Sep 17 00:00:00 2001 From: Aorimn Date: Wed, 4 Feb 2015 21:31:55 +0100 Subject: [PATCH 106/109] Add Irker service Irker is a gateway which sends IRC messages on git updates. This new service provides an interface to this gateway, integrated in Gitlab, for each updates. As per the guidelines, this commit adds the new feature in the CHANGELOG, tests and documentation. See http://www.catb.org/esr/irker/ --- CHANGELOG | 1 + .../projects/services_controller.rb | 3 +- app/models/project.rb | 1 + app/models/project_services/irker_service.rb | 152 ++++++++++++++++ app/models/service.rb | 3 +- app/workers/irker_worker.rb | 169 ++++++++++++++++++ doc/project_services/irker.md | 46 +++++ doc/project_services/project_services.md | 1 + features/project/service.feature | 6 + features/steps/project/services.rb | 17 ++ .../project_services/irker_service_spec.rb | 103 +++++++++++ 11 files changed, 500 insertions(+), 2 deletions(-) create mode 100644 app/models/project_services/irker_service.rb create mode 100644 app/workers/irker_worker.rb create mode 100644 doc/project_services/irker.md create mode 100644 spec/models/project_services/irker_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index dae32953cd..ee862a4ca3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 7.9.0 (unreleased) - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) - Fix mass-unassignment of issues (Robert Speicher) - Allow user confirmation to be skipped for new users via API + - Add a service to send updates to an Irker gateway (Romain Coltel) v 7.8.1 - Fix run of custom post receive hooks diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 5c29a6550f..e7823020e6 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -50,7 +50,8 @@ class Projects::ServicesController < Projects::ApplicationController :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :build_key, :server, :teamcity_url, :build_type, - :description, :issues_url, :new_issue_url, :restrict_to_branch + :description, :issues_url, :new_issue_url, :restrict_to_branch, + :colorize_messages, :channels ) end end diff --git a/app/models/project.rb b/app/models/project.rb index 7f2e0b4c17..907f331d8f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -73,6 +73,7 @@ class Project < ActiveRecord::Base has_one :gitlab_ci_service, dependent: :destroy has_one :campfire_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy + has_one :irker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb new file mode 100644 index 0000000000..a0203a5bb1 --- /dev/null +++ b/app/models/project_services/irker_service.rb @@ -0,0 +1,152 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) + +require 'uri' + +class IrkerService < Service + prop_accessor :colorize_messages, :recipients, :channels + validates :recipients, presence: true, if: :activated? + validate :check_recipients_count, if: :activated? + + before_validation :get_channels + after_initialize :initialize_settings + + # Writer for RSpec tests + attr_writer :settings + + def initialize_settings + # See the documentation (doc/project_services/irker.md) for possible values + # here + @settings ||= { + server_ip: 'localhost', + server_port: 6659, + max_channels: 3, + default_irc_uri: nil + } + end + + def title + 'Irker (IRC gateway)' + end + + def description + 'Send IRC messages, on update, to a list of recipients through an Irker '\ + 'gateway.' + end + + def help + msg = 'Recipients have to be specified with a full URI: '\ + 'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\ + 'the channel to be a nickname instead, append ",isnick" to the channel '\ + 'name; if the channel is protected by a secret password, append '\ + '"?key=secretpassword" to the URI.' + + unless @settings[:default_irc].nil? + msg += ' Note that a default IRC URI is provided by this service\'s '\ + "administrator: #{default_irc}. You can thus just give a channel name." + end + msg + end + + def to_param + 'irker' + end + + def execute(push_data) + IrkerWorker.perform_async(project_id, channels, + colorize_messages, push_data, @settings) + end + + def fields + [ + { type: 'textarea', name: 'recipients', + placeholder: 'Recipients/channels separated by whitespaces' }, + { type: 'checkbox', name: 'colorize_messages' }, + ] + end + + private + + def check_recipients_count + return true if recipients.nil? || recipients.empty? + + if recipients.split(/\s+/).count > max_chans + errors.add(:recipients, "are limited to #{max_chans}") + end + end + + def max_chans + @settings[:max_channels] + end + + def get_channels + return true unless :activated? + return true if recipients.nil? || recipients.empty? + + map_recipients + + errors.add(:recipients, 'are all invalid') if channels.empty? + true + end + + def map_recipients + self.channels = recipients.split(/\s+/).map do |recipient| + format_channel default_irc_uri, recipient + end + channels.reject! &:nil? + end + + def default_irc_uri + default_irc = @settings[:default_irc_uri] + if !(default_irc.nil? || default_irc[-1] == '/') + default_irc += '/' + end + default_irc + end + + def format_channel(default_irc, recipient) + cnt = 0 + url = nil + + # Try to parse the chan as a full URI + begin + uri = URI.parse(recipient) + raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0 + rescue URI::InvalidURIError + unless default_irc.nil? + cnt += 1 + recipient = "#{default_irc}#{recipient}" + retry if cnt == 1 + end + else + url = consider_uri uri + end + url + end + + def consider_uri(uri) + # Authorize both irc://domain.com/#chan and irc://domain.com/chan + if uri.is_a?(URI) && uri.scheme[/^ircs?$/] && !uri.path.nil? + # Do not authorize irc://domain.com/ + if uri.fragment.nil? && uri.path.length > 1 + uri.to_s + else + # Authorize irc://domain.com/smthg#chan + # The irker daemon will deal with it by concatenating smthg and + # chan, thus sending messages on #smthgchan + uri.to_s + end + end + end +end diff --git a/app/models/service.rb b/app/models/service.rb index f87d875c10..f4e97da321 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -100,7 +100,8 @@ class Service < ActiveRecord::Base def self.available_services_names %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla asana - emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker) + emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira + redmine custom_issue_tracker irker) end def self.create_from_template(project_id, template) diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb new file mode 100644 index 0000000000..613bae351d --- /dev/null +++ b/app/workers/irker_worker.rb @@ -0,0 +1,169 @@ +require 'json' +require 'socket' + +class IrkerWorker + include Sidekiq::Worker + + def perform(project_id, chans, colors, push_data, settings) + project = Project.find(project_id) + + # Get config parameters + return false unless init_perform settings, chans, colors + + repo_name = push_data['repository']['name'] + committer = push_data['user_name'] + branch = push_data['ref'].gsub(%r'refs/[^/]*/', '') + + if @colors + repo_name = "\x0304#{repo_name}\x0f" + branch = "\x0305#{branch}\x0f" + end + + # Firsts messages are for branch creation/deletion + send_branch_updates push_data, project, repo_name, committer, branch + + # Next messages are for commits + send_commits push_data, project, repo_name, committer, branch + + close_connection + true + end + + private + + def init_perform(set, chans, colors) + @colors = colors + @channels = chans + start_connection set['server_ip'], set['server_port'] + end + + def start_connection(irker_server, irker_port) + begin + @socket = TCPSocket.new irker_server, irker_port + rescue Errno::ECONNREFUSED => e + logger.fatal "Can't connect to Irker daemon: #{e}" + return false + end + true + end + + def sendtoirker(privmsg) + to_send = { to: @channels, privmsg: privmsg } + @socket.puts JSON.dump(to_send) + end + + def close_connection + @socket.close + end + + def send_branch_updates(push_data, project, repo_name, committer, branch) + if push_data['before'] =~ /^000000/ + send_new_branch project, repo_name, committer, branch + elsif push_data['after'] =~ /^000000/ + send_del_branch repo_name, committer, branch + end + end + + def send_new_branch(project, repo_name, committer, branch) + repo_path = project.path_with_namespace + newbranch = "#{Gitlab.config.gitlab.url}/#{repo_path}/branches" + newbranch = "\x0302\x1f#{newbranch}\x0f" if @colors + + privmsg = "[#{repo_name}] #{committer} has created a new branch " + privmsg += "#{branch}: #{newbranch}" + sendtoirker privmsg + end + + def send_del_branch(repo_name, committer, branch) + privmsg = "[#{repo_name}] #{committer} has deleted the branch #{branch}" + sendtoirker privmsg + end + + def send_commits(push_data, project, repo_name, committer, branch) + return if push_data['total_commits_count'] == 0 + + # Next message is for number of commit pushed, if any + if push_data['before'] =~ /^000000/ + # Tweak on push_data["before"] in order to have a nice compare URL + push_data['before'] = before_on_new_branch push_data, project + end + + send_commits_count(push_data, project, repo_name, committer, branch) + + # One message per commit, limited by 3 messages (same limit as the + # github irc hook) + commits = push_data['commits'].first(3) + commits.each do |hook_attrs| + send_one_commit project, hook_attrs, repo_name, branch + end + end + + def before_on_new_branch(push_data, project) + commit = commit_from_id project, push_data['commits'][0]['id'] + parents = commit.parents + # Return old value if there's no new one + return push_data['before'] if parents.empty? + # Or return the first parent-commit + parents[0].id + end + + def send_commits_count(data, project, repo, committer, branch) + url = compare_url data, project.path_with_namespace + commits = colorize_commits data['total_commits_count'] + + new_commits = 'new commit' + new_commits += 's' if data['total_commits_count'] > 1 + + sendtoirker "[#{repo}] #{committer} pushed #{commits} #{new_commits} " \ + "to #{branch}: #{url}" + end + + def compare_url(data, repo_path) + sha1 = Commit::truncate_sha(data['before']) + sha2 = Commit::truncate_sha(data['after']) + compare_url = "#{Gitlab.config.gitlab.url}/#{repo_path}/compare" + compare_url += "/#{sha1}...#{sha2}" + colorize_url compare_url + end + + def send_one_commit(project, hook_attrs, repo_name, branch) + commit = commit_from_id project, hook_attrs['id'] + sha = colorize_sha Commit::truncate_sha(hook_attrs['id']) + author = hook_attrs['author']['name'] + files = colorize_nb_files(files_count commit) + title = commit.title + + sendtoirker "#{repo_name}/#{branch} #{sha} #{author} (#{files}): #{title}" + end + + def commit_from_id(project, id) + commit = Gitlab::Git::Commit.find(project.repository, id) + Commit.new(commit) + end + + def files_count(commit) + files = "#{commit.diffs.count} file" + files += 's' if commit.diffs.count > 1 + files + end + + def colorize_sha(sha) + sha = "\x0314#{sha}\x0f" if @colors + sha + end + + def colorize_nb_files(nb_files) + nb_files = "\x0312#{nb_files}\x0f" if @colors + nb_files + end + + def colorize_url(url) + url = "\x0302\x1f#{url}\x0f" if @colors + url + end + + def colorize_commits(commits) + commits = "\x02#{commits}\x0f" if @colors + commits + end +end diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md new file mode 100644 index 0000000000..780a45bca2 --- /dev/null +++ b/doc/project_services/irker.md @@ -0,0 +1,46 @@ +# Irker IRC Gateway + +GitLab provides a way to push update messages to an Irker server. When +configured, pushes to a project will trigger the service to send data directly +to the Irker server. + +See the project homepage for further info: http://www.catb.org/esr/irker/ + +## Needed setup + +You will first need an Irker daemon. You can download the Irker code from its +gitorious repository on https://gitorious.org/irker: `git clone +git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can +run the python script named `irkerd`. This script is the gateway script, it acts +both as an IRC client, for sending messages to an IRC server obviously, and as a +TCP server, for receiving messages from the GitLab service. + +If the Irker server runs on the same machine, you are done. If not, you will +need to follow the firsts steps of the next section. + +## Optional setup + +In the `app/models/project_services/irker_service.rb` file, you can modify some +options in the `initialize_settings` method: +- **server_ip** (defaults to `localhost`): the server IP address where the +`irkerd` daemon runs; +- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon; +- **max_channels** (defaults to `3`): the maximum number of recipients the +client is authorized to join, per project; +- **default_irc_uri** (no default) : if this option is set, it has to be in the +format `irc[s]://domain.name` and will be prepend to each and every channel +provided by the user which is not a full URI. + +If the Irker server and the GitLab application do not run on the same host, you +will **need** to setup at least the **server_ip** option. + +## Note on Irker recipients + +Irker accepts channel names of the form `chan` and `#chan`, both for the +`#chan` channel. If you want to send messages in query, you will need to add +`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter +case, `Aorimn` is treated as a nick and no more as a channel name. + +Irker can also join password-protected channels. Users need to append +`?key=thesecretpassword` to the chan name. + diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md index 93a57485cf..86eda341d6 100644 --- a/doc/project_services/project_services.md +++ b/doc/project_services/project_services.md @@ -13,6 +13,7 @@ __Project integrations with external services for continuous integration and mor - Gemnasium - GitLab CI - HipChat +- [Irker](irker.md) An IRC gateway to receive messages on repository updates. - Pivotal Tracker - Pushover - Slack diff --git a/features/project/service.feature b/features/project/service.feature index d0600aca01..fdff640ec8 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -61,6 +61,12 @@ Feature: Project Services And I fill email on push settings Then I should see email on push service settings saved + Scenario: Activate Irker (IRC Gateway) service + When I visit project "Shop" services page + And I click Irker service link + And I fill Irker settings + Then I should see Irker service settings saved + Scenario: Activate Atlassian Bamboo CI service When I visit project "Shop" services page And I click Atlassian Bamboo CI service link diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 3307117e69..4b3d79324a 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -17,6 +17,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps page.should have_content 'Atlassian Bamboo' page.should have_content 'JetBrains TeamCity' page.should have_content 'Asana' + page.should have_content 'Irker (IRC gateway)' end step 'I click gitlab-ci service link' do @@ -132,6 +133,22 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps find_field('Recipients').value.should == 'qa@company.name' end + step 'I click Irker service link' do + click_link 'Irker (IRC gateway)' + end + + step 'I fill Irker settings' do + check 'Active' + fill_in 'Recipients', with: 'irc://chat.freenode.net/#commits' + check 'Colorize messages' + click_button 'Save' + end + + step 'I should see Irker service settings saved' do + find_field('Recipients').value.should == 'irc://chat.freenode.net/#commits' + find_field('Colorize messages').value.should == '1' + end + step 'I click Slack service link' do click_link 'Slack' end diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb new file mode 100644 index 0000000000..bbd5245ad3 --- /dev/null +++ b/spec/models/project_services/irker_service_spec.rb @@ -0,0 +1,103 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +require 'spec_helper' +require 'socket' +require 'json' + +describe IrkerService do + describe 'Associations' do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe 'Validations' do + before do + subject.active = true + subject.properties['recipients'] = _recipients + end + + context 'active' do + let(:_recipients) { nil } + it { should validate_presence_of :recipients } + end + + context 'too many recipients' do + let(:_recipients) { 'a b c d' } + it 'should add an error if there is too many recipients' do + subject.send :check_recipients_count + subject.errors.should_not be_blank + end + end + + context '3 recipients' do + let(:_recipients) { 'a b c' } + it 'should not add an error if there is 3 recipients' do + subject.send :check_recipients_count + subject.errors.should be_blank + end + end + end + + describe 'Execute' do + let(:irker) { IrkerService.new } + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + let(:recipients) { '#commits' } + let(:colorize_messages) { '1' } + + before do + irker.stub( + active: true, + project: project, + project_id: project.id, + service_hook: true, + properties: { + 'recipients' => recipients, + 'colorize_messages' => colorize_messages + } + ) + irker.settings = { + server_ip: 'localhost', + server_port: 6659, + max_channels: 3, + default_irc_uri: 'irc://chat.freenode.net/' + } + irker.valid? + @irker_server = TCPServer.new 'localhost', 6659 + end + + after do + @irker_server.close + end + + it 'should send valid JSON messages to an Irker listener' do + irker.execute(sample_data) + + conn = @irker_server.accept + conn.readlines.each do |line| + msg = JSON.load(line.chomp("\n")) + msg.keys.should match_array(['to', 'privmsg']) + if msg['to'].is_a?(String) + msg['to'].should == 'irc://chat.freenode.net/#commits' + else + msg['to'].should match_array(['irc://chat.freenode.net/#commits']) + end + end + conn.close + end + end +end From 4bc5c66fe12a2dc5d8fe9ed5878da5dea2444442 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 1 Mar 2015 20:15:59 -0800 Subject: [PATCH 107/109] Fix broken `project_url` routing when protected branches are accessed with an empty repo --- app/helpers/gitlab_routing_helper.rb | 16 ++++++++++++++++ .../protected_branches_controller_spec.rb | 10 ++++++++++ 2 files changed, 26 insertions(+) create mode 100644 spec/controllers/projects/protected_branches_controller_spec.rb diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f0eb50a0e1..ac37f909ce 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -28,4 +28,20 @@ module GitlabRoutingHelper def merge_request_path(entity, *args) namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args) end + + def project_url(project, *args) + namespace_project_url(project.namespace, project, *args) + end + + def edit_project_url(project, *args) + edit_namespace_project_url(project.namespace, project, *args) + end + + def issue_url(entity, *args) + namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args) + end + + def merge_request_url(entity, *args) + namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args) + end end diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb new file mode 100644 index 0000000000..596d8d34b7 --- /dev/null +++ b/spec/controllers/projects/protected_branches_controller_spec.rb @@ -0,0 +1,10 @@ +require('spec_helper') + +describe Projects::ProtectedBranchesController do + describe "GET #index" do + let(:project) { create(:project_empty_repo, :public) } + it "redirect empty repo to projects page" do + get(:index, namespace_id: project.namespace.to_param, project_id: project.to_param) + end + end +end From 8d0690c5c768415a5dae1155c236f7650ea894cf Mon Sep 17 00:00:00 2001 From: Nicolas Bouilleaud Date: Wed, 17 Dec 2014 15:26:43 +0100 Subject: [PATCH 108/109] Support names starting with a digit or _ for projects and users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is what’s actually allowed when creating a user or a project in gitlab. --- CHANGELOG | 1 + lib/gitlab/markdown.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index dae32953cd..6bd93b8cd4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 7.8.1 - Fix urls for the issues when relative url was enabled - Add Bitbucket omniauth provider. - Add Bitbucket importer. + - Support referencing issues to a project whose name starts with a digit v 7.8.0 - Fix access control and protection against XSS for note attachments and other uploads. diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index a1fd794aed..d85c2ee4f2 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -121,7 +121,7 @@ module Gitlab text end - NAME_STR = '[a-zA-Z][a-zA-Z0-9_\-\.]*' + NAME_STR = '[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*' PROJ_STR = "(?#{NAME_STR}/#{NAME_STR})" REFERENCE_PATTERN = %r{ From 643afcbe00b766f786e6c7bac6cbd55870159df1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 2 Mar 2015 14:02:09 -0800 Subject: [PATCH 109/109] Reduce amount of sql queries on dashboard projects page --- app/controllers/dashboard_controller.rb | 2 +- app/views/dashboard/projects.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index eca7b39bcd..4930029e16 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -47,7 +47,7 @@ class DashboardController < ApplicationController @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? - @projects = @projects.includes(:namespace) + @projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]).per(30) diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 15db859254..03d4b3d8bb 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -16,7 +16,7 @@ %li.my-project-row %h4.project-title .pull-left - = project_icon("#{project.namespace.to_param}/#{project.to_param}", alt: '', class: 'avatar project-avatar s60') + = project_icon(project, alt: '', class: 'avatar project-avatar s60') .project-access-icon = visibility_level_icon(project.visibility_level) = link_to project_path(project), class: dom_class(project) do