diff --git a/CHANGELOG b/CHANGELOG index 26cef6c6c1e..520a25d8aba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,10 +3,10 @@ Note: The upcoming release contains empty lines to reduce the number of merge co v 7.8.0 - 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 - Expose description in groups API - - - - + - Better UI for project services page + - Cleaner UI for web editor - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger) - - @@ -17,8 +17,10 @@ v 7.8.0 - Show tags in commit view (Hannes Rosenögger) - Only count a user's vote once on a merge request or issue (Michael Clarke) - + - Increate font size when browse source files and diffs + - Create new file in empty repository using GitLab UI - - - + - Ability to clone project using oauth2 token - - Upgrade Sidekiq gem to version 3.3.0 - Stop git zombie creation during force push check @@ -29,11 +31,13 @@ v 7.8.0 - - - Allow configuring protection of the default branch upon first push (Marco Wessel) + - + - + - Add a commit calendar to the user profile (Hannes Rosenögger) - - - - - - - + - Fix long broadcast message cut-off on left sidebar (Visay Keo) - Add Project Avatars (Steven Thonus and Hannes Rosenögger) - - @@ -59,6 +63,7 @@ v 7.8.0 - - - + - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) v 7.7.1 - Improve mention autocomplete performance diff --git a/Gemfile b/Gemfile index 96a1097d6d8..a0f5b70de32 100644 --- a/Gemfile +++ b/Gemfile @@ -154,6 +154,9 @@ gem "slack-notifier", "~> 1.0.0" # d3 gem "d3_rails", "~> 3.1.4" +#cal-heatmap +gem "cal-heatmap-rails", "~> 0.0.1" + # underscore-rails gem "underscore-rails", "~> 1.4.4" @@ -170,7 +173,7 @@ gem 'ace-rails-ap' gem 'mousetrap-rails' # Semantic UI Sass for Sidebar -gem 'semantic-ui-sass', '~> 0.16.1.0' +gem 'semantic-ui-sass', '~> 1.8.0' gem "sass-rails", '~> 4.0.2' gem "coffee-rails" diff --git a/Gemfile.lock b/Gemfile.lock index 18fae9b7001..4b5b718c87e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,6 +52,7 @@ GEM sass (~> 3.2) browser (0.7.2) builder (3.2.2) + cal-heatmap-rails (0.0.1) capybara (2.2.1) mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -490,7 +491,7 @@ GEM activesupport (>= 3.1, < 4.2) select2-rails (3.5.2) thor (~> 0.14) - semantic-ui-sass (0.16.1.0) + semantic-ui-sass (1.8.0.0) sass (~> 3.2) settingslogic (2.0.9) sexp_processor (4.4.0) @@ -627,6 +628,7 @@ DEPENDENCIES binding_of_caller bootstrap-sass (~> 3.0) browser + cal-heatmap-rails (~> 0.0.1) capybara (~> 2.2.1) carrierwave coffee-rails @@ -715,7 +717,7 @@ DEPENDENCIES sdoc seed-fu select2-rails - semantic-ui-sass (~> 0.16.1.0) + semantic-ui-sass (~> 1.8.0) settingslogic shoulda-matchers (~> 2.1.0) sidekiq (~> 3.3) diff --git a/app/assets/images/no_project_icon.png b/app/assets/images/no_project_icon.png deleted file mode 100644 index 8e9529c67ec..00000000000 Binary files a/app/assets/images/no_project_icon.png and /dev/null differ diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 337170605dc..4912c534b0e 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -39,6 +39,7 @@ #= require shortcuts_dashboard_navigation #= require shortcuts_issueable #= require shortcuts_network +#= require cal-heatmap #= require_tree . window.slugify = (text) -> diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob/blob.js.coffee similarity index 100% rename from app/assets/javascripts/blob.js.coffee rename to app/assets/javascripts/blob/blob.js.coffee diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee new file mode 100644 index 00000000000..6914ca759f6 --- /dev/null +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -0,0 +1,44 @@ +class @EditBlob + constructor: (assets_path, mode)-> + ace.config.set "modePath", assets_path + '/ace' + ace.config.loadModule "ace/ext/searchbox" + if mode + ace_mode = mode + editor = ace.edit("editor") + editor.focus() + @editor = editor + + if ace_mode + editor.getSession().setMode "ace/mode/" + ace_mode + + disableButtonIfEmptyField "#commit_message", ".js-commit-button" + $(".js-commit-button").click -> + $("#file-content").val editor.getValue() + $(".file-editor form").submit() + return + + editModePanes = $(".js-edit-mode-pane") + editModeLinks = $(".js-edit-mode a") + editModeLinks.click (event) -> + event.preventDefault() + currentLink = $(this) + paneId = currentLink.attr("href") + currentPane = editModePanes.filter(paneId) + editModeLinks.parent().removeClass "active hover" + currentLink.parent().addClass "active hover" + editModePanes.hide() + if paneId is "#preview" + currentPane.fadeIn 200 + $.post currentLink.data("preview-url"), + content: editor.getValue() + , (response) -> + currentPane.empty().append response + return + + else + currentPane.fadeIn 200 + editor.focus() + return + + editor: -> + return @editor diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee new file mode 100644 index 00000000000..a6e27116b40 --- /dev/null +++ b/app/assets/javascripts/blob/new_blob.js.coffee @@ -0,0 +1,21 @@ +class @NewBlob + constructor: (assets_path, mode)-> + ace.config.set "modePath", assets_path + '/ace' + ace.config.loadModule "ace/ext/searchbox" + if mode + ace_mode = mode + editor = ace.edit("editor") + editor.focus() + @editor = editor + + if ace_mode + editor.getSession().setMode "ace/mode/" + ace_mode + + disableButtonIfEmptyField "#commit_message", ".js-commit-button" + $(".js-commit-button").click -> + $("#file-content").val editor.getValue() + $(".file-editor form").submit() + return + + editor: -> + return @editor diff --git a/app/assets/javascripts/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee new file mode 100644 index 00000000000..6a0d5e43567 --- /dev/null +++ b/app/assets/javascripts/calendar.js.coffee @@ -0,0 +1,35 @@ +class @calendar + options = + month: "short" + day: "numeric" + year: "numeric" + + constructor: (timestamps, starting_year, starting_month) -> + cal = new CalHeatMap() + cal.init + itemName: ["commit"] + data: timestamps + domain: "year" + subDomain: "month" + start: new Date(starting_year, starting_month) + domainLabelFormat: "%b" + id: "cal-heatmap" + domain: "month" + subDomain: "day" + range: 12 + tooltip: true + domainDynamicDimension: false + colLimit: 4 + label: + position: "top" + domainMargin: 1 + legend: [ + 0 + 1 + 4 + 7 + ] + legendCellPadding: 3 + onClick: (date, count) -> + return + return diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee index f4a2ca813d2..836269c44f9 100644 --- a/app/assets/javascripts/project_new.js.coffee +++ b/app/assets/javascripts/project_new.js.coffee @@ -9,17 +9,3 @@ class @ProjectNew initEvents: -> disableButtonIfEmptyField '#project_name', '.project-submit' - - $('#project_issues_enabled').change -> - if ($(this).is(':checked') == true) - $('#project_issues_tracker').removeAttr('disabled') - else - $('#project_issues_tracker').attr('disabled', 'disabled') - - $('#project_issues_tracker').change() - - $('#project_issues_tracker').change -> - if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) - $('#project_issues_tracker_id').attr('disabled', 'disabled') - else - $('#project_issues_tracker_id').removeAttr('disabled') diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee index 02a7d7b731d..d0eaaad92b8 100644 --- a/app/assets/javascripts/project_show.js.coffee +++ b/app/assets/javascripts/project_show.js.coffee @@ -6,7 +6,7 @@ class @ProjectShow new Flash('Star toggle failed. Try again later.', 'alert') $("a[data-toggle='tab']").on "shown.bs.tab", (e) -> - $.cookie "default_view", $(e.target).attr("href") + $.cookie "default_view", $(e.target).attr("href"), { expires: 30 } defaultView = $.cookie("default_view") if defaultView diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 3cf08782c3c..8f63a7fee64 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -8,6 +8,7 @@ *= require select2 *= require_self *= require dropzone/basic + *= require cal-heatmap */ @import "main/*"; diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index d098f1ecaa2..3b360275065 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -173,6 +173,11 @@ margin-right: 0px; } } + + &.btn-lg { + font-size: 15px; + line-height: 1.4; + } } .btn-block { diff --git a/app/assets/stylesheets/generic/calendar.scss b/app/assets/stylesheets/generic/calendar.scss new file mode 100644 index 00000000000..9483b26164e --- /dev/null +++ b/app/assets/stylesheets/generic/calendar.scss @@ -0,0 +1,95 @@ +.calendar_onclick_placeholder { + padding: 0 0 2px 0; +} + +.calendar_commit_activity { + padding: 5px 0 0; +} + +.calendar_onclick_second { + font-size: 14px; + display: block; +} + +.calendar_onclick_hr { + padding: 0; + margin: 10px 0; +} + +.calendar_commit_date { + color: #999; +} + +.calendar_activity_summary { + font-size: 14px; +} + +/** +* This overwrites the default values of the cal-heatmap gem +*/ +.calendar { + .qi { + background-color: #999; + fill: #fff; + } + + .q1 { + background-color: #dae289; + fill: #ededed; + } + + .q2 { + background-color: #cedb9c; + fill: #ACD5F2; + } + + .q3 { + background-color: #b5cf6b; + fill: #7FA8D1; + } + + .q4 { + background-color: #637939; + fill: #49729B; + } + + .q5 { + background-color: #3b6427; + fill: #254E77; + } + + .domain-background { + fill: none; + shape-rendering: crispedges; + } + + .ch-tooltip { + position: absolute; + display: none; + margin-top: 22px; + margin-left: 1px; + font-size: 13px; + padding: 3px; + font-weight: 550; + background-color: #222; + span { + position: absolute; + width: 200px; + text-align: center; + visibility: hidden; + border-radius: 10px; + &:after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + margin-left: -8px; + width: 0; + height: 0; + border-top: 8px solid #000000; + border-right: 8px solid transparent; + border-left: 8px solid transparent; + } + } + } +} diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss index 83dc7ab491a..e1ca86af816 100644 --- a/app/assets/stylesheets/generic/highlight.scss +++ b/app/assets/stylesheets/generic/highlight.scss @@ -10,8 +10,8 @@ border: none; border-radius: 0; font-family: $monospace_font; - font-size: 12px !important; - line-height: 16px !important; + font-size: $code_font_size !important; + line-height: $code_line_height !important; margin: 0; overflow: auto; overflow-y: hidden; @@ -38,8 +38,8 @@ a { font-family: $monospace_font; display: block; - font-size: 12px !important; - line-height: 16px !important; + font-size: $code_font_size !important; + line-height: $code_line_height !important; white-space: nowrap; i { diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss index 2a68d922bb7..6efa56544a5 100644 --- a/app/assets/stylesheets/gl_bootstrap.scss +++ b/app/assets/stylesheets/gl_bootstrap.scss @@ -1,9 +1,6 @@ /* * Twitter bootstrap with GitLab customizations/additions * - * Some unused bootstrap compontents like panels are not included. - * Other components like tabs are modified to GitLab style. - * */ $font-size-base: 13px !default; diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/main/mixins.scss index 8435d1dae79..e54482d14c3 100644 --- a/app/assets/stylesheets/main/mixins.scss +++ b/app/assets/stylesheets/main/mixins.scss @@ -139,7 +139,7 @@ } @mixin panel-colored { - border: none; + border: 1px solid #EEE; background: $box_bg; @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss index 6bbce70a782..acbf5be94a3 100644 --- a/app/assets/stylesheets/main/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -59,3 +59,5 @@ $list-font-size: 15px; $sidebar_width: 230px; $avatar_radius: 50%; +$code_font_size: 13px; +$code_line_height: 1.5; diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index 00795f990bf..90010781af0 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -112,3 +112,7 @@ color: #FFF; } } + +.dash-list .str-truncated { + max-width: 72%; +} diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss index 758f15c8013..da50dbe4715 100644 --- a/app/assets/stylesheets/sections/diff.scss +++ b/app/assets/stylesheets/sections/diff.scss @@ -37,7 +37,7 @@ overflow-y: hidden; background: #FFF; color: #333; - font-size: 12px; + font-size: $code_font_size; .old { span.idiff { background-color: #F99; @@ -64,8 +64,8 @@ margin: 0px; padding: 0px; td { - line-height: 18px; - font-size: 12px; + line-height: $code_line_height; + font-size: $code_font_size; } } diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/sections/editor.scss index f62f46ee168..88aa256e56e 100644 --- a/app/assets/stylesheets/sections/editor.scss +++ b/app/assets/stylesheets/sections/editor.scss @@ -31,4 +31,26 @@ margin: 5px 8px 0 8px; } } + + .file-title { + @extend .monospace; + font-size: 14px; + padding: 5px; + } + + .editor-ref { + background: #f5f5f5; + padding: 11px 15px; + border-right: 1px solid #CCC; + display: inline-block; + margin: -5px -5px; + margin-right: 10px; + } + + .editor-file-name { + .new-file-name { + display: inline-block; + width: 200px; + } + } } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index 047617e54ba..e255cbcada8 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -138,9 +138,10 @@ header { top: -1px; padding-right: 0px !important; img { - width: 26px; - height: 26px; - @include border-radius($avatar_radius); + width: 50px; + height: 50px; + margin: -15px; + margin-left: 5px; } } diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index fbfd9c8cd9b..7a9d3334d96 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -163,8 +163,9 @@ form.edit-issue { } } -.issue-title { +h3.issue-title { margin-top: 0; + font-size: 2em; } .context .select2-container { diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index 8bd32f41e2c..0e27c389387 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -122,6 +122,7 @@ background: $box_bg; margin-bottom: 20px; color: #666; + border: 1px solid #EEE; @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); .ci_widget { diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 93c0c2bc518..0a7671e3feb 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -16,6 +16,8 @@ .project-home-panel { margin-bottom: 15px; + position: relative; + padding-left: 85px; &.empty-project { border-bottom: 0px; @@ -23,6 +25,22 @@ margin-bottom: 0px; } + .project-identicon-holder { + position: absolute; + left: 0; + + .avatar { + width: 70px; + height: 70px; + @include border-radius(0px); + } + + .identicon { + font-size: 45px; + line-height: 1.6; + } + } + .project-home-dropdown { margin-left: 10px; float: right; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ad13a0ac3e4..36e13706768 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base end def add_gon_variables - gon.default_issues_tracker = Project.issues_tracker.default_value + gon.default_issues_tracker = Project.new.default_issue_tracker.to_param gon.api_version = API::API.version gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb deleted file mode 100644 index a7b1b7b40e8..00000000000 --- a/app/controllers/projects/base_tree_controller.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Projects::BaseTreeController < Projects::ApplicationController - include ExtractsPath - - before_filter :authorize_download_code! - before_filter :require_non_empty_project -end - diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index 367d1295f34..106f21b83e6 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -2,7 +2,7 @@ class Projects::BlameController < Projects::ApplicationController include ExtractsPath - # Authorize + before_filter :assign_ref_vars before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 2412800c493..b471d57f698 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -2,16 +2,70 @@ class Projects::BlobController < Projects::ApplicationController include ExtractsPath - # Authorize - before_filter :authorize_download_code! - before_filter :require_non_empty_project - before_filter :authorize_push_code!, only: [:destroy] + # Raised when given an invalid file path + class InvalidPathError < StandardError; end - before_filter :blob + before_filter :authorize_download_code! + before_filter :require_non_empty_project, except: [:new, :create] + before_filter :authorize_push_code!, only: [:destroy] + before_filter :assign_blob_vars + before_filter :commit, except: [:new, :create] + before_filter :blob, except: [:new, :create] + before_filter :from_merge_request, only: [:edit, :update] + before_filter :after_edit_path, only: [:edit, :update] + before_filter :require_branch_head, only: [:edit, :update] + + def new + commit unless @repository.empty? + end + + def create + file_path = File.join(@path, File.basename(params[:file_name])) + result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully committed" + redirect_to project_blob_path(@project, File.join(@ref, file_path)) + else + flash[:alert] = result[:message] + render :new + end + end def show end + def edit + @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha + end + + def update + result = Files::UpdateService. + new(@project, current_user, params, @ref, @path).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully committed" + + if from_merge_request + from_merge_request.reload_code + end + + redirect_to after_edit_path + else + flash[:alert] = result[:message] + render :edit + end + end + + def preview + @content = params[:content] + diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', + include_diff_info: true) + @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/)) + + render layout: false + end + def destroy result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute @@ -46,10 +100,44 @@ class Projects::BlobController < Projects::ApplicationController if @blob @blob - elsif tree.entries.any? - redirect_to project_tree_path(@project, File.join(@ref, @path)) and return else + if tree = @repository.tree(@commit.id, @path) + if tree.entries.any? + redirect_to project_tree_path(@project, File.join(@ref, @path)) and return + end + end + return not_found! end end + + def commit + @commit = @repository.commit(@ref) + + return not_found! unless @commit + end + + def assign_blob_vars + @id = params[:id] + @ref, @path = extract_ref(@id) + + + rescue InvalidPathError + not_found! + end + + def after_edit_path + @after_edit_path ||= + if from_merge_request + diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) + + "#file-path-#{hexdigest(@path)}" + else + project_blob_path(@project, @id) + end + end + + def from_merge_request + # If blob edit was initiated from merge request page + @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) + end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 9476b6c0284..0a85c36a758 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -3,7 +3,7 @@ require "base64" class Projects::CommitsController < Projects::ApplicationController include ExtractsPath - # Authorize + before_filter :assign_ref_vars before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb deleted file mode 100644 index 65661c80410..00000000000 --- a/app/controllers/projects/edit_tree_controller.rb +++ /dev/null @@ -1,60 +0,0 @@ -class Projects::EditTreeController < Projects::BaseTreeController - before_filter :require_branch_head - before_filter :blob - before_filter :authorize_push_code! - before_filter :from_merge_request - before_filter :after_edit_path - - def show - @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha - end - - def update - result = Files::UpdateService. - new(@project, current_user, params, @ref, @path).execute - - if result[:status] == :success - flash[:notice] = "Your changes have been successfully committed" - - if from_merge_request - from_merge_request.reload_code - end - - redirect_to after_edit_path - else - flash[:alert] = result[:message] - render :show - end - end - - def preview - @content = params[:content] - - diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', - include_diff_info: true) - @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/)) - - render layout: false - end - - private - - def blob - @blob ||= @repository.blob_at(@commit.id, @path) - end - - def after_edit_path - @after_edit_path ||= - if from_merge_request - diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) + - "#file-path-#{hexdigest(@path)}" - else - project_blob_path(@project, @id) - end - end - - def from_merge_request - # If blob edit was initiated from merge request page - @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) - end -end diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index ada1aed0df7..59f2a745367 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -2,7 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController include ExtractsPath include ApplicationHelper - # Authorize + before_filter :assign_ref_vars before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb deleted file mode 100644 index ffba706b2f6..00000000000 --- a/app/controllers/projects/new_tree_controller.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Projects::NewTreeController < Projects::BaseTreeController - before_filter :require_branch_head - before_filter :authorize_push_code! - - def show - end - - def update - file_path = File.join(@path, File.basename(params[:file_name])) - result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute - - if result[:status] == :success - flash[:notice] = "Your changes have been successfully committed" - redirect_to project_blob_path(@project, File.join(@ref, file_path)) - else - flash[:alert] = result[:message] - render :show - end - end -end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index fdbc4c5a098..84888265dc1 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -2,7 +2,7 @@ class Projects::RawController < Projects::ApplicationController include ExtractsPath - # Authorize + before_filter :assign_ref_vars before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 67665f5f601..cede0ebe0ae 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -1,7 +1,7 @@ class Projects::RefsController < Projects::ApplicationController include ExtractsPath - # Authorize + before_filter :assign_ref_vars before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 5ac6947c5de..5b35cc90413 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController def index @project.build_missing_services - @services = @project.services.reload + @services = @project.services.visible.reload end def edit @@ -17,7 +17,8 @@ class Projects::ServicesController < Projects::ApplicationController def update if @service.update_attributes(service_params) - redirect_to edit_project_service_path(@project, @service.to_param) + redirect_to edit_project_service_path(@project, @service.to_param), + notice: 'Successfully updated.' else render 'edit' end @@ -45,7 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController :title, :token, :type, :active, :api_key, :subdomain, :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, - :build_key, :server, :teamcity_url, :build_type + :build_key, :server, :teamcity_url, :build_type, + :description, :issues_url, :new_issue_url ) end end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 4d033b36848..5b52640a4e1 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -1,7 +1,12 @@ # Controller for viewing a repository's file structure -class Projects::TreeController < Projects::BaseTreeController - def show +class Projects::TreeController < Projects::ApplicationController + include ExtractsPath + before_filter :assign_ref_vars + before_filter :authorize_download_code! + before_filter :require_non_empty_project, except: [:new, :create] + + def show if tree.entries.empty? if @repository.blob_at(@commit.id, @path) redirect_to project_blob_path(@project, File.join(@ref, @path)) and return diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 67af1801bda..ff5e31067fb 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,16 +1,12 @@ class UsersController < ApplicationController - skip_before_filter :authenticate_user!, only: [:show] + skip_before_filter :authenticate_user! + before_filter :set_user layout :determine_layout def show - @user = User.find_by_username!(params[:username]) - - unless current_user || @user.public_profile? - return authenticate_user! - end - # Projects user can view - authorized_projects_ids = ProjectsFinder.new.execute(current_user).pluck(:id) + visible_projects = ProjectsFinder.new.execute(current_user) + authorized_projects_ids = visible_projects.pluck(:id) @projects = @user.personal_projects. where(id: authorized_projects_ids) @@ -30,6 +26,19 @@ class UsersController < ApplicationController end end + def calendar + visible_projects = ProjectsFinder.new.execute(current_user) + + # Get user repositories and collect timestamps for commits + user_repositories = visible_projects.map(&:repository) + calendar = Gitlab::CommitsCalendar.new(user_repositories, @user) + @timestamps = calendar.timestamps + @starting_year = (Time.now - 1.year).strftime("%Y") + @starting_month = Date.today.strftime("%m").to_i + + render 'calendar', layout: false + end + def determine_layout if current_user 'navless' @@ -37,4 +46,14 @@ class UsersController < ApplicationController 'public_users' end end + + private + + def set_user + @user = User.find_by_username!(params[:username]) + + unless current_user || @user.public_profile? + return authenticate_user! + end + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f253ae91306..104ae517a08 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -56,8 +56,6 @@ module ApplicationHelper image_tag project.avatar.url, options elsif project.avatar_in_git image_tag project_avatar_path(project), options - elsif options[:only_uploaded] - image_tag '/assets/no_project_icon.png', options else # generated icon project_identicon(project, options) end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 3a282803963..e75eebd2da9 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -19,4 +19,42 @@ module BlobHelper def no_highlight_files %w(credits changelog copying copyright license authors) end + + def edit_blob_link(project, ref, path, options = {}) + blob = + begin + project.repository.blob_at(ref, path) + rescue + nil + end + + if blob && blob.text? + text = 'Edit' + after = options[:after] || '' + from_mr = options[:from_merge_request_id] + link_opts = {} + link_opts[:from_merge_request_id] = from_mr if from_mr + cls = 'btn btn-small' + if allowed_tree_edit?(project, ref) + link_to text, project_edit_blob_path(project, tree_join(ref, path), + link_opts), class: cls + else + content_tag :span, text, class: cls + ' disabled' + end + after.html_safe + else + '' + end + end + + def leave_edit_message + "Leave edit mode?\nAll unsaved changes will be lost." + end + + def editing_preview_title(filename) + if Gitlab::MarkdownHelper.previewable?(filename) + 'Preview' + else + 'Preview changes' + end + end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index bcf108c5c48..9fe183e6e2f 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -16,45 +16,25 @@ module IssuesHelper def url_for_project_issues(project = @project) return '' if project.nil? - if project.used_default_issues_tracker? || !external_issues_tracker_enabled? - project_issues_path(project) - else - url = Gitlab.config.issues_tracker[project.issues_tracker]['project_url'] - url.gsub(':project_id', project.id.to_s). - gsub(':issues_tracker_id', project.issues_tracker_id.to_s) - end + project.issues_tracker.project_url end def url_for_new_issue(project = @project) return '' if project.nil? - if project.used_default_issues_tracker? || !external_issues_tracker_enabled? - url = new_project_issue_path project_id: project - else - issues_tracker = Gitlab.config.issues_tracker[project.issues_tracker] - url = issues_tracker['new_issue_url'] - url.gsub(':project_id', project.id.to_s). - gsub(':issues_tracker_id', project.issues_tracker_id.to_s) - end + project.issues_tracker.new_issue_url end def url_for_issue(issue_iid, project = @project) return '' if project.nil? - if project.used_default_issues_tracker? || !external_issues_tracker_enabled? - url = project_issue_url project_id: project, id: issue_iid - else - url = Gitlab.config.issues_tracker[project.issues_tracker]['issues_url'] - url.gsub(':id', issue_iid.to_s). - gsub(':project_id', project.id.to_s). - gsub(':issues_tracker_id', project.issues_tracker_id.to_s) - end + project.issues_tracker.issue_url(issue_iid) end def title_for_issue(issue_iid, project = @project) return '' if project.nil? - if project.used_default_issues_tracker? + if project.default_issues_tracker? issue = project.issues.where(iid: issue_iid).first return issue.title if issue end @@ -77,11 +57,6 @@ module IssuesHelper ts.html_safe end - # Checks if issues_tracker setting exists in gitlab.yml - def external_issues_tracker_enabled? - Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any? - end - def bulk_update_milestone_options options_for_select(['None (backlog)']) + options_from_collection_for_select(project_active_milestones, 'id', diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index de232ab4e25..351641e19af 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -72,18 +72,6 @@ module ProjectsHelper @project.milestones.active.order("due_date, title ASC") end - def project_issues_trackers(current_tracker = nil) - values = Project.issues_tracker.values.map do |tracker_key| - if tracker_key.to_sym == :gitlab - ['GitLab', tracker_key] - else - [Gitlab.config.issues_tracker[tracker_key]['title'] || tracker_key, tracker_key] - end - end - - options_for_select(values, current_tracker) - end - def link_to_toggle_star(title, starred, signed_in) cls = 'star-btn' cls << ' disabled' unless signed_in @@ -187,7 +175,13 @@ module ProjectsHelper "Issues - " + title end elsif current_controller?(:blob) - "#{@project.path}\/#{@blob.path} at #{@ref} - " + title + if current_action?(:new) || current_action?(:create) + "New file at #{@ref}" + elsif current_action?(:show) + "#{@blob.path} at #{@ref}" + elsif @blob + "Edit file #{@blob.path} at #{@ref}" + end elsif current_controller?(:commits) "Commits at #{@ref} - " + title elsif current_controller?(:merge_requests) @@ -257,7 +251,7 @@ module ProjectsHelper end def github_import_enabled? - Gitlab.config.omniauth.enabled && enabled_oauth_providers.include?(:github) + enabled_oauth_providers.include?(:github) end end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 1d987a6ffc0..727ec3fb231 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -64,32 +64,6 @@ module TreeHelper ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref) end - def edit_blob_link(project, ref, path, options = {}) - blob = - begin - project.repository.blob_at(ref, path) - rescue - nil - end - - if blob && blob.text? - text = 'Edit' - after = options[:after] || '' - from_mr = options[:from_merge_request_id] - link_opts = {} - link_opts[:from_merge_request_id] = from_mr if from_mr - cls = 'btn btn-small' - if allowed_tree_edit?(project, ref) - link_to text, project_edit_tree_path(project, tree_join(ref, path), - link_opts), class: cls - else - content_tag :span, text, class: cls + ' disabled' - end + after.html_safe - else - '' - end - end - def tree_breadcrumbs(tree, max_links = 2) if @path.present? part_path = "" @@ -121,16 +95,4 @@ module TreeHelper return tree.name end end - - def leave_edit_message - "Leave edit mode?\nAll unsaved changes will be lost." - end - - def editing_preview_title(filename) - if Gitlab::MarkdownHelper.previewable?(filename) - 'Preview' - else - 'Diff' - end - end end diff --git a/app/models/group.rb b/app/models/group.rb index 733afa2fc07..e098dfb3cdf 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -25,6 +25,9 @@ class Group < Namespace mount_uploader :avatar, AttachmentUploader + after_create :post_create_hook + after_destroy :post_destroy_hook + def human_name name end @@ -74,6 +77,18 @@ class Group < Namespace projects.public_only.any? end + def post_create_hook + system_hook_service.execute_hooks_for(self, :create) + end + + def post_destroy_hook + system_hook_service.execute_hooks_for(self, :destroy) + end + + def system_hook_service + SystemHooksService.new + end + class << self def search(query) where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%") diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index b7f296b13fb..28d0b4483b4 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -27,8 +27,9 @@ class GroupMember < Member scope :with_group, ->(group) { where(source_id: group.id) } scope :with_user, ->(user) { where(user_id: user.id) } - after_create :notify_create + after_create :post_create_hook after_update :notify_update + after_destroy :post_destroy_hook def self.access_level_roles Gitlab::Access.options_with_owner @@ -42,8 +43,9 @@ class GroupMember < Member access_level end - def notify_create + def post_create_hook notification_service.new_group_member(self) + system_hook_service.execute_hooks_for(self, :create) end def notify_update @@ -52,6 +54,14 @@ class GroupMember < Member end end + def post_destroy_hook + system_hook_service.execute_hooks_for(self, :destroy) + end + + def system_hook_service + SystemHooksService.new + end + def notification_service NotificationService.new end diff --git a/app/models/project.rb b/app/models/project.rb index 97f23227484..b26c697a7b7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -74,7 +74,13 @@ class Project < ActiveRecord::Base has_one :bamboo_service, dependent: :destroy has_one :teamcity_service, dependent: :destroy has_one :pushover_service, dependent: :destroy - has_one :forked_project_link, dependent: :destroy, foreign_key: 'forked_to_project_id' + has_one :jira_service, dependent: :destroy + has_one :redmine_service, dependent: :destroy + has_one :custom_issue_tracker_service, dependent: :destroy + has_one :gitlab_issue_tracker_service, dependent: :destroy + + has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" + has_one :forked_from_project, through: :forked_project_link # Merge Requests for target project should be removed with it has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' @@ -144,8 +150,6 @@ class Project < ActiveRecord::Base scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :non_archived, -> { where(archived: false) } - enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab - state_machine :import_status, initial: :none do event :import_start do transition [:none, :finished] => :started @@ -305,19 +309,43 @@ class Project < ActiveRecord::Base end def issue_exists?(issue_id) - if used_default_issues_tracker? + if default_issues_tracker? self.issues.where(iid: issue_id).first.present? else true end end - def used_default_issues_tracker? - self.issues_tracker == Project.issues_tracker.default_value + def default_issue_tracker + gitlab_issue_tracker_service ||= create_gitlab_issue_tracker_service + end + + def issues_tracker + if external_issue_tracker + external_issue_tracker + else + default_issue_tracker + end + end + + def default_issues_tracker? + if external_issue_tracker + false + else + true + end + end + + def external_issues_trackers + services.select(&:issue_tracker?).reject(&:default?) + end + + def external_issue_tracker + @external_issues_tracker ||= external_issues_trackers.select(&:activated?).first end def can_have_issues_tracker_id? - self.issues_enabled && !self.used_default_issues_tracker? + self.issues_enabled && !self.default_issues_tracker? end def build_missing_services @@ -332,7 +360,7 @@ class Project < ActiveRecord::Base def available_services_names %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla - emails_on_push gemnasium slack pushover buildbox bamboo teamcity) + emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker) end def gitlab_ci? diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb new file mode 100644 index 00000000000..2476b62da89 --- /dev/null +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -0,0 +1,38 @@ +class CustomIssueTrackerService < IssueTrackerService + + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + def title + if self.properties && self.properties['title'].present? + self.properties['title'] + else + 'Custom Issue Tracker' + end + end + + def description + if self.properties && self.properties['description'].present? + self.properties['description'] + else + 'Custom issue tracker' + end + end + + def to_param + 'custom_issue_tracker' + end + + def fields + [ + { type: 'text', name: 'title', placeholder: title }, + { type: 'text', name: 'description', placeholder: description }, + { type: 'text', name: 'project_url', placeholder: 'Project url' }, + { type: 'text', name: 'issues_url', placeholder: 'Issue url'}, + { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url'} + ] + end + + def initialize_properties + self.properties = {} if properties.nil? + end +end diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb new file mode 100644 index 00000000000..25f5f23bdf9 --- /dev/null +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -0,0 +1,25 @@ +class GitlabIssueTrackerService < IssueTrackerService + include Rails.application.routes.url_helpers + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + + def default? + true + end + + def to_param + 'gitlab' + end + + def project_url + project_issues_path(project) + end + + def new_issue_url + new_project_issue_path project_id: project + end + + def issue_url(iid) + "#{Gitlab.config.gitlab.url}#{project_issue_path(project_id: project, id: iid)}" + end +end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb new file mode 100644 index 00000000000..632f053d17b --- /dev/null +++ b/app/models/project_services/issue_tracker_service.rb @@ -0,0 +1,74 @@ +class IssueTrackerService < Service + + validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated? + + def category + :issue_tracker + end + + def default? + false + end + + def project_url + # implement inside child + end + + def issues_url + # implement inside child + end + + def new_issue_url + # implement inside child + end + + def issue_url(iid) + self.issues_url.gsub(':id', iid.to_s) + end + + def fields + [ + { type: 'text', name: 'description', placeholder: description }, + { type: 'text', name: 'project_url', placeholder: 'Project url' }, + { type: 'text', name: 'issues_url', placeholder: 'Issue url'}, + { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url'} + ] + end + + def initialize_properties + if properties.nil? + if enabled_in_gitlab_config + self.properties = { + title: issues_tracker['title'], + project_url: set_project_url, + issues_url: issues_tracker['issues_url'], + new_issue_url: issues_tracker['new_issue_url'] + } + else + self.properties = {} + end + end + end + + private + + def enabled_in_gitlab_config + Gitlab.config.issues_tracker && + Gitlab.config.issues_tracker.values.any? && + issues_tracker + end + + def issues_tracker + Gitlab.config.issues_tracker[to_param] + end + + def set_project_url + id = self.project.issues_tracker_id + + if id + issues_tracker['project_url'].gsub(":issues_tracker_id", id) + else + issues_tracker['project_url'] + end + end +end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb new file mode 100644 index 00000000000..b0d668948d0 --- /dev/null +++ b/app/models/project_services/jira_service.rb @@ -0,0 +1,24 @@ +class JiraService < IssueTrackerService + + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + def title + if self.properties && self.properties['title'].present? + self.properties['title'] + else + 'JIRA' + end + end + + def description + if self.properties && self.properties['description'].present? + self.properties['description'] + else + 'Jira issue tracker' + end + end + + def to_param + 'jira' + end +end diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb new file mode 100644 index 00000000000..11cce3e0561 --- /dev/null +++ b/app/models/project_services/redmine_service.rb @@ -0,0 +1,24 @@ +class RedmineService < IssueTrackerService + + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + def title + if self.properties && self.properties['title'].present? + self.properties['title'] + else + 'Redmine' + end + end + + def description + if self.properties && self.properties['description'].present? + self.properties['description'] + else + 'Redmine issue tracker' + end + end + + def to_param + 'redmine' + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb index e93c76790c7..f6400f7aff1 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -139,8 +139,10 @@ class Repository def graph_log Rails.cache.fetch(cache_key(:graph_log)) do - commits = raw_repository.log(limit: 6000, skip_merges: true, + commits = raw_repository.log(limit: 6000, + skip_merges: true, ref: root_ref) + commits.map do |rugged_commit| commit = Gitlab::Git::Commit.new(rugged_commit) @@ -148,12 +150,32 @@ class Repository author_name: commit.author_name.force_encoding('UTF-8'), author_email: commit.author_email.force_encoding('UTF-8'), additions: commit.stats.additions, - deletions: commit.stats.deletions + deletions: commit.stats.deletions, } end end end + def timestamps_by_user_log(user) + args = %W(git log --author=#{user.email} --since=#{(Date.today - 1.year).to_s} --pretty=format:%cd --date=short) + dates = Gitlab::Popen.popen(args, path_to_repo).first.split("\n") + + if dates.present? + dates + else + [] + end + end + + def commits_per_day_for_user(user) + timestamps_by_user_log(user). + group_by { |commit_date| commit_date }. + inject({}) do |hash, (timestamp_date, commits)| + hash[timestamp_date] = commits.count + hash + end + end + def cache_key(type) "#{type}:#{path_with_namespace}" end diff --git a/app/models/service.rb b/app/models/service.rb index 71c8aa39e45..15948e63e41 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -26,6 +26,8 @@ class Service < ActiveRecord::Base validates :project_id, presence: true + scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } + def activated? active end @@ -86,4 +88,12 @@ class Service < ActiveRecord::Base def async_execute(data) Sidekiq::Client.enqueue(ProjectServiceWorker, id, data) end + + def issue_tracker? + self.category == :issue_tracker + end + + def self.issue_tracker_service_list + Service.select(&:issue_tracker?).map{ |s| s.to_param } + end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index b90adeef00a..2c457ef2cef 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -9,10 +9,6 @@ module Files return error("You are not allowed to create file in this branch") end - unless repository.branch_names.include?(ref) - return error("You can only create files if you are on top of a branch") - end - file_name = File.basename(path) file_path = path @@ -23,12 +19,21 @@ module Files ) end - blob = repository.blob_at_branch(ref, file_path) + if project.empty_repo? + # everything is ok because repo does not have a commits yet + else + unless repository.branch_names.include?(ref) + return error("You can only create files if you are on top of a branch") + end - if blob - return error("Your changes could not be committed, because file with such name exists") + blob = repository.blob_at_branch(ref, file_path) + + if blob + return error("Your changes could not be committed, because file with such name exists") + end end + new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) created_successfully = new_file_action.commit!( params[:content], diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index ffed13a12e1..f670019cc63 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -2,9 +2,9 @@ module Issues class CloseService < Issues::BaseService def execute(issue, commit = nil) if issue.close - notification_service.close_issue(issue, current_user) event_service.close_issue(issue, current_user) create_note(issue, commit) + notification_service.close_issue(issue, current_user) execute_hooks(issue, 'close') end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 0ee9635ed99..83e413d7248 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -23,8 +23,8 @@ module Issues end if issue.previous_changes.include?('assignee_id') - notification_service.reassigned_issue(issue, current_user) create_assignee_note(issue) + notification_service.reassigned_issue(issue, current_user) end issue.notice_added_references(issue.project, current_user) diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb index b5d90a74e15..378b39bb9d6 100644 --- a/app/services/merge_requests/auto_merge_service.rb +++ b/app/services/merge_requests/auto_merge_service.rb @@ -11,9 +11,9 @@ module MergeRequests if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) merge_request.merge - notification_service.merge_mr(merge_request, current_user) create_merge_event(merge_request, current_user) create_note(merge_request) + notification_service.merge_mr(merge_request, current_user) execute_hooks(merge_request) true diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index 4249a84f382..47454f9f0c2 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -7,8 +7,8 @@ module MergeRequests if merge_request.close event_service.close_mr(merge_request, current_user) - notification_service.close_mr(merge_request, current_user) create_note(merge_request) + notification_service.close_mr(merge_request, current_user) execute_hooks(merge_request, 'close') end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 1e1614028f7..327ead4ff3f 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -9,9 +9,9 @@ module MergeRequests def execute(merge_request, commit_message) merge_request.merge - notification_service.merge_mr(merge_request, current_user) create_merge_event(merge_request, current_user) create_note(merge_request) + notification_service.merge_mr(merge_request, current_user) execute_hooks(merge_request, 'merge') true diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index a2a9c933f63..8279ad2001b 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -3,8 +3,8 @@ module MergeRequests def execute(merge_request) if merge_request.reopen event_service.reopen_mr(merge_request, current_user) - notification_service.reopen_mr(merge_request, current_user) create_note(merge_request) + notification_service.reopen_mr(merge_request, current_user) execute_hooks(merge_request, 'reopen') merge_request.reload_code merge_request.mark_as_unchecked diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 56c8510e0ae..10c401756eb 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -33,8 +33,8 @@ module MergeRequests end if merge_request.previous_changes.include?('assignee_id') - notification_service.reassigned_merge_request(merge_request, current_user) create_assignee_note(merge_request) + notification_service.reassigned_merge_request(merge_request, current_user) end merge_request.notice_added_references(merge_request.project, current_user) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 72c9149378e..2fc63b9f4b7 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -314,15 +314,7 @@ class NotificationService end def new_resource_email(target, project, method) - if target.respond_to?(:participants) - recipients = target.participants - else - recipients = [] - end - - recipients = reject_muted_users(recipients, project) - recipients = reject_mention_users(recipients, project) - recipients = recipients.concat(project_watchers(project)).uniq + recipients = build_recipients(target, project) recipients.delete(target.author) recipients.each do |recipient| @@ -331,9 +323,7 @@ class NotificationService end def close_resource_email(target, project, current_user, method) - recipients = reject_muted_users([target.author, target.assignee], project) - recipients = reject_mention_users(recipients, project) - recipients = recipients.concat(project_watchers(project)).uniq + recipients = build_recipients(target, project) recipients.delete(current_user) recipients.each do |recipient| @@ -343,17 +333,7 @@ class NotificationService def reassign_resource_email(target, project, current_user, method) assignee_id_was = previous_record(target, "assignee_id") - - recipients = User.where(id: [target.assignee_id, assignee_id_was]) - - # Add watchers to email list - recipients = recipients.concat(project_watchers(project)) - - # reject users with disabled notifications - recipients = reject_muted_users(recipients, project) - recipients = reject_mention_users(recipients, project) - - # Reject me from recipients if I reassign an item + recipients = build_recipients(target, project) recipients.delete(current_user) recipients.each do |recipient| @@ -362,9 +342,7 @@ class NotificationService end def reopen_resource_email(target, project, current_user, method, status) - recipients = reject_muted_users([target.author, target.assignee], project) - recipients = reject_mention_users(recipients, project) - recipients = recipients.concat(project_watchers(project)).uniq + recipients = build_recipients(target, project) recipients.delete(current_user) recipients.each do |recipient| @@ -372,6 +350,20 @@ class NotificationService end end + def build_recipients(target, project) + recipients = + if target.respond_to?(:participants) + target.participants + else + [target.author, target.assignee] + end + + recipients = reject_muted_users(recipients, project) + recipients = reject_mention_users(recipients, project) + recipients = recipients.concat(project_watchers(project)).uniq + recipients + end + def mailer Notify.delay end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 44e494525b3..46f6e91e808 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -60,6 +60,26 @@ class SystemHooksService access_level: model.human_access, project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase }) + when Group + owner = model.owner + + data.merge!( + name: model.name, + path: model.path, + group_id: model.id, + owner_name: owner.respond_to?(:name) ? owner.name : nil, + owner_email: owner.respond_to?(:email) ? owner.email : nil, + ) + when GroupMember + data.merge!( + group_name: model.group.name, + group_path: model.group.path, + group_id: model.group.id, + user_name: model.user.name, + user_email: model.user.email, + user_id: model.user.id, + group_access: model.human_access, + ) end end @@ -68,6 +88,9 @@ class SystemHooksService when ProjectMember return "user_add_to_team" if event == :create return "user_remove_from_team" if event == :destroy + when GroupMember + return 'user_add_to_group' if event == :create + return 'user_remove_from_group' if event == :destroy else "#{model.class.name.downcase}_#{event.to_s}" end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index dd95af426c4..32e0e4a6848 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -32,7 +32,7 @@ %span.light.pull-right = Milestone.count %p - Active users last 30 days + Users who signed in during last 30 days %span.light.pull-right = User.where("current_sign_in_at > ?", 30.days.ago).count .col-md-4 diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 61383315373..c7976ba564f 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -11,5 +11,4 @@ - elsif event.note? = render "events/event/note", event: event - else - = render "events/event/common", event: event - + = render "events/event/common", event: event \ No newline at end of file diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml index e590ddbf931..ed00153de7e 100644 --- a/app/views/groups/_new_group_member.html.haml +++ b/app/views/groups/_new_group_member.html.haml @@ -5,7 +5,11 @@ .form-group = f.label :access_level, "Group Access", class: 'control-label' - .col-sm-10= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @users_group.access_level), class: "project-access-select select2" + .col-sm-10 + = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @users_group.access_level), class: "project-access-select select2" + .help-block + Read more about role permissions + %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" .form-actions = f.submit 'Add users into group', class: "btn btn-create" diff --git a/app/views/groups/_settings_nav.html.haml b/app/views/groups/_settings_nav.html.haml index 35180792a0d..e6aee22e529 100644 --- a/app/views/groups/_settings_nav.html.haml +++ b/app/views/groups/_settings_nav.html.haml @@ -1,11 +1,11 @@ %ul.sidebar-subnav = nav_link(path: 'groups#edit') do - = link_to edit_group_path(@group) do + = link_to edit_group_path(@group), title: 'Group' do %i.fa.fa-pencil-square-o %span Group = nav_link(path: 'groups#projects') do - = link_to projects_group_path(@group) do + = link_to projects_group_path(@group), title: 'Projects' do %i.fa.fa-folder %span Projects diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index bdf27562c26..77bfe4f996e 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -43,6 +43,6 @@ %i.fa.fa-sign-out %li.hidden-xs = link_to current_user, class: "profile-pic", id: 'profile-pic' do - = image_tag avatar_icon(current_user.email, 26), alt: 'User activity' + = image_tag avatar_icon(current_user.email, 60), alt: 'User activity' = render 'shared/outdated_browser' diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 621365fa6aa..1263f44eca9 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,5 +1,6 @@ - if defined?(sidebar) .page-with-sidebar + = render "layouts/broadcast" .sidebar-wrapper = render(sidebar) .content-wrapper diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index fb62d5fea0a..dc8652cb145 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -2,6 +2,5 @@ %html{ lang: "en"} = render "layouts/head", title: "Admin area" %body{class: "#{app_theme} #{theme_type} admin", :'data-page' => body_data_page} - = render "layouts/broadcast" = render "layouts/head_panel", title: "Admin area" = render 'layouts/page', sidebar: 'layouts/nav/admin' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index d40c9753b10..e5420a13605 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -2,6 +2,5 @@ %html{ lang: "en"} = render "layouts/head", title: "Dashboard" %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page } - = render "layouts/broadcast" = render "layouts/head_panel", title: "Dashboard" = render 'layouts/page', sidebar: 'layouts/nav/dashboard' diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 72b0d03908d..98edcf3a140 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -2,6 +2,5 @@ %html{ lang: "en"} = render "layouts/head", title: group_head_title %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} - = render "layouts/broadcast" = render "layouts/head_panel", title: @group.name = render 'layouts/page', sidebar: 'layouts/nav/group' diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index d9c6670d1bc..4813a4f16f5 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -5,49 +5,50 @@ %span Overview = nav_link(controller: :projects) do - = link_to admin_projects_path do + = link_to admin_projects_path, title: 'Projects' do %i.fa.fa-cube %span Projects = nav_link(controller: :users) do - = link_to admin_users_path do + = link_to admin_users_path, title: 'Users' do %i.fa.fa-user %span Users = nav_link(controller: :groups) do - = link_to admin_groups_path do + = link_to admin_groups_path, title: 'Groups' do %i.fa.fa-group %span Groups = nav_link(controller: :logs) do - = link_to admin_logs_path do + = link_to admin_logs_path, title: 'Logs' do %i.fa.fa-file-text %span Logs = nav_link(controller: :broadcast_messages) do - = link_to admin_broadcast_messages_path do + = link_to admin_broadcast_messages_path, title: 'Broadcast Messages' do %i.fa.fa-bullhorn %span Messages = nav_link(controller: :hooks) do - = link_to admin_hooks_path do + = link_to admin_hooks_path, title: 'Hooks' do %i.fa.fa-external-link %span Hooks = nav_link(controller: :background_jobs) do - = link_to admin_background_jobs_path do + = link_to admin_background_jobs_path, title: 'Background Jobs' do %i.fa.fa-cog %span Background Jobs - = nav_link(controller: :application_settings) do - = link_to admin_application_settings_path do + = nav_link(controller: :applications) do + = link_to admin_applications_path, title: 'Applications' do + %i.fa.fa-cloud + %span + Applications + + = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do + = link_to admin_application_settings_path, title: 'Settings' do %i.fa.fa-cogs %span Settings - = nav_link(controller: :applications) do - = link_to admin_applications_path do - %i.fa.fa-cloud - %span - Applications diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index a2eaa2d83c5..48c7c999427 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -5,24 +5,24 @@ %span Activity = nav_link(path: 'dashboard#projects') do - = link_to projects_dashboard_path, class: 'shortcuts-projects' do + = link_to projects_dashboard_path, title: 'Projects', class: 'shortcuts-projects' do %i.fa.fa-cube %span Projects = nav_link(path: 'dashboard#issues') do - = link_to assigned_issues_dashboard_path, class: 'shortcuts-issues' do + = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do %i.fa.fa-exclamation-circle %span Issues %span.count= current_user.assigned_issues.opened.count = nav_link(path: 'dashboard#merge_requests') do - = link_to assigned_mrs_dashboard_path, class: 'shortcuts-merge_requests' do + = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do %i.fa.fa-tasks %span Merge Requests %span.count= current_user.assigned_merge_requests.opened.count = nav_link(controller: :help) do - = link_to help_path do + = link_to help_path, title: 'Help' do %i.fa.fa-question-circle %span Help diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 54468d077ab..ddd3df19eec 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -6,33 +6,33 @@ Activity - if current_user = nav_link(controller: [:group, :milestones]) do - = link_to group_milestones_path(@group) do + = link_to group_milestones_path(@group), title: 'Milestones' do %i.fa.fa-clock-o %span Milestones = nav_link(path: 'groups#issues') do - = link_to issues_group_path(@group) do + = link_to issues_group_path(@group), title: 'Issues' do %i.fa.fa-exclamation-circle %span Issues - if current_user %span.count= Issue.opened.of_group(@group).count = nav_link(path: 'groups#merge_requests') do - = link_to merge_requests_group_path(@group) do + = link_to merge_requests_group_path(@group), title: 'Merge Requests' do %i.fa.fa-tasks %span Merge Requests - if current_user %span.count= MergeRequest.opened.of_group(@group).count = nav_link(path: 'groups#members') do - = link_to members_group_path(@group) do + = link_to members_group_path(@group), title: 'Members' do %i.fa.fa-users %span Members - if can?(current_user, :manage_group, @group) = nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do - = link_to edit_group_path(@group), class: "tab no-highlight" do + = link_to edit_group_path(@group), title: 'Settings', class: "tab no-highlight" do %i.fa.fa-cogs %span Settings diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index cc50b9b570a..0914d2a167a 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -5,52 +5,51 @@ %span Profile = nav_link(controller: :accounts) do - = link_to profile_account_path do + = link_to profile_account_path, title: 'Account' do %i.fa.fa-gear %span Account = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do - = link_to applications_profile_path do + = link_to applications_profile_path, title: 'Applications' do %i.fa.fa-cloud %span Applications = nav_link(controller: :emails) do - = link_to profile_emails_path do + = link_to profile_emails_path, title: 'Emails' do %i.fa.fa-envelope-o %span Emails %span.count= current_user.emails.count + 1 - unless current_user.ldap_user? = nav_link(controller: :passwords) do - = link_to edit_profile_password_path do + = link_to edit_profile_password_path, title: 'Password' do %i.fa.fa-lock %span Password = nav_link(controller: :notifications) do - = link_to profile_notifications_path do + = link_to profile_notifications_path, title: 'Notifications' do %i.fa.fa-inbox %span Notifications = nav_link(controller: :keys) do - = link_to profile_keys_path do + = link_to profile_keys_path, title: 'SSH Keys' do %i.fa.fa-key %span SSH Keys %span.count= current_user.keys.count = nav_link(path: 'profiles#design') do - = link_to design_profile_path do + = link_to design_profile_path, title: 'Design' do %i.fa.fa-image %span Design = nav_link(controller: :groups) do - = link_to profile_groups_path do + = link_to profile_groups_path, title: 'Groups' do %i.fa.fa-group %span Groups = nav_link(path: 'profiles#history') do - = link_to history_profile_path do + = link_to history_profile_path, title: 'History' do %i.fa.fa-history %span History - diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 94cee0bd50f..6c2d5966cbe 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -6,45 +6,44 @@ Project - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do - = link_to project_tree_path(@project, @ref || @repository.root_ref), class: 'shortcuts-tree' do + = link_to project_tree_path(@project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree' do %i.fa.fa-files-o %span Files - - if project_nav_tab? :commits = nav_link(controller: %w(commit commits compare repositories tags branches)) do - = link_to project_commits_path(@project, @ref || @repository.root_ref), class: 'shortcuts-commits' do + = link_to project_commits_path(@project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits' do %i.fa.fa-history %span Commits - if project_nav_tab? :network = nav_link(controller: %w(network)) do - = link_to project_network_path(@project, @ref || @repository.root_ref), class: 'shortcuts-network' do + = link_to project_network_path(@project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network' do %i.fa.fa-code-fork %span Network - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do - = link_to project_graph_path(@project, @ref || @repository.root_ref), class: 'shortcuts-graphs' do + = link_to project_graph_path(@project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs' do %i.fa.fa-area-chart %span Graphs - if project_nav_tab? :issues = nav_link(controller: %w(issues milestones labels)) do - = link_to url_for_project_issues, class: 'shortcuts-issues' do + = link_to url_for_project_issues, title: 'Issues', class: 'shortcuts-issues' do %i.fa.fa-exclamation-circle %span Issues - - if @project.used_default_issues_tracker? + - if @project.default_issues_tracker? %span.count.issue_counter= @project.issues.opened.count - if project_nav_tab? :merge_requests = nav_link(controller: :merge_requests) do - = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do + = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do %i.fa.fa-tasks %span Merge Requests @@ -52,21 +51,21 @@ - if project_nav_tab? :wiki = nav_link(controller: :wikis) do - = link_to project_wiki_path(@project, :home), class: 'shortcuts-wiki' do + = link_to project_wiki_path(@project, :home), title: 'Wiki', class: 'shortcuts-wiki' do %i.fa.fa-book %span Wiki - if project_nav_tab? :snippets = nav_link(controller: :snippets) do - = link_to project_snippets_path(@project), class: 'shortcuts-snippets' do + = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do %i.fa.fa-file-text-o %span Snippets - if project_nav_tab? :settings = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do - = link_to edit_project_path(@project), 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/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 941084cc4ad..89d816061e2 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -2,6 +2,5 @@ %html{ lang: "en"} = render "layouts/head", title: "Profile" %body{class: "#{app_theme} #{theme_type} profile", :'data-page' => body_data_page} - = render "layouts/broadcast" = render "layouts/head_panel", title: "Profile" = 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 0f20bf38bfd..d2c9c2a991c 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -2,7 +2,6 @@ %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 } - = render "layouts/broadcast" = 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 d4ee53db55c..c44a40c9c12 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -2,7 +2,6 @@ %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 } - = render "layouts/broadcast" = 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 64794104ac5..ae3d2bd8a89 100644 --- a/app/views/layouts/public_group.html.haml +++ b/app/views/layouts/public_group.html.haml @@ -2,6 +2,5 @@ %html{ lang: "en"} = render "layouts/head", title: group_head_title %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} - = render "layouts/broadcast" = render "layouts/public_head_panel", title: "group: #{@group.name}" = 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 5964a29d522..027e9a53139 100644 --- a/app/views/layouts/public_projects.html.haml +++ b/app/views/layouts/public_projects.html.haml @@ -2,6 +2,5 @@ %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} - = render "layouts/broadcast" = 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 0510ce34a7f..37767df33d2 100644 --- a/app/views/layouts/public_users.html.haml +++ b/app/views/layouts/public_users.html.haml @@ -2,6 +2,5 @@ %html{ lang: "en"} = render "layouts/head", title: @title %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} - = render "layouts/broadcast" = render "layouts/public_head_panel", title: @title = render 'layouts/page' diff --git a/app/views/projects/_blob_editor.html.haml b/app/views/projects/_blob_editor.html.haml deleted file mode 100644 index 1fb74b55c41..00000000000 --- a/app/views/projects/_blob_editor.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -.file-holder.file - .file-title - %i.icon-file - %span.file_name - %span.monospace.light #{ref} - - if local_assigns[:path] - = ': ' + local_assigns[:path] - .file-content.code - %pre.js-edit-mode-pane#editor - = params[:content] || local_assigns[:blob_data] - - if local_assigns[:path] - .js-edit-mode-pane#preview.hide - .center - %h2 - %i.icon-spinner.icon-spin diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 05910c6038c..2ed49f83a7a 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,8 +1,9 @@ - empty_repo = @project.empty_repo? .project-home-panel{:class => ("empty-project" if empty_repo)} + .project-identicon-holder + = project_icon(@project.to_param, alt: '', class: 'avatar') .project-home-row .project-home-desc - = project_icon(@project.to_param, alt: '', class: 'avatar s32') - if @project.description.present? = escaped_autolink(@project.description) - if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml index 64eda0bf286..646e48a1e1d 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -1,31 +1,31 @@ %ul.project-settings-nav.sidebar-subnav = nav_link(path: 'projects#edit') do - = link_to edit_project_path(@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 = nav_link(controller: [:team_members, :teams]) do - = link_to project_team_index_path(@project), class: "team-tab tab" do + = link_to project_team_index_path(@project), title: 'Members', class: "team-tab tab" do %i.fa.fa-users %span Members = nav_link(controller: :deploy_keys) do - = link_to project_deploy_keys_path(@project) do + = link_to project_deploy_keys_path(@project), title: 'Deploy Keys' do %i.fa.fa-key %span Deploy Keys = nav_link(controller: :hooks) do - = link_to project_hooks_path(@project) do + = link_to project_hooks_path(@project), title: 'Web Hooks' do %i.fa.fa-link %span Web Hooks = nav_link(controller: :services) do - = link_to project_services_path(@project) do + = link_to project_services_path(@project), title: 'Services' do %i.fa.fa-cogs %span Services = nav_link(controller: :protected_branches) do - = link_to project_protected_branches_path(@project) do + = link_to project_protected_branches_path(@project), title: 'Protected Branches' do %i.fa.fa-lock %span Protected branches diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml new file mode 100644 index 00000000000..96f188e4aa7 --- /dev/null +++ b/app/views/projects/blob/_editor.html.haml @@ -0,0 +1,25 @@ +.file-holder.file + .file-title + .editor-ref + %i.fa.fa-code-fork + = ref + %span.editor-file-name + - if @path + %span.monospace + = @path + + - if current_action?(:new) || current_action?(:create) + \/ + = text_field_tag 'file_name', params[:file_name], placeholder: "File name", + required: true, class: 'form-control new-file-name' + .pull-right + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' + + .file-content.code + %pre.js-edit-mode-pane#editor + = params[:content] || local_assigns[:blob_data] + - if local_assigns[:path] + .js-edit-mode-pane#preview.hide + .center + %h2 + %i.icon-spinner.icon-spin diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml new file mode 100644 index 00000000000..b150b639888 --- /dev/null +++ b/app/views/projects/blob/edit.html.haml @@ -0,0 +1,24 @@ +.file-editor + %ul.nav.nav-tabs.js-edit-mode + %li.active + = link_to '#editor' do + %i.fa.fa-edit + Edit file + + %li + = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do + %i.fa.fa-eye + = editing_preview_title(@blob.name) + + = form_tag(project_update_blob_path(@project, @id), method: :put, class: "form-horizontal") do + = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data + = render 'shared/commit_message_container', params: params, + placeholder: "Update #{@blob.name}" + = 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] + = render 'projects/commit_button', ref: @ref, + cancel_path: @after_edit_path + +:javascript + blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml new file mode 100644 index 00000000000..df6aedbe17d --- /dev/null +++ b/app/views/projects/blob/new.html.haml @@ -0,0 +1,12 @@ +%h3.page-title New file +.file-editor + = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'form-horizontal form-new-file') do + = render 'projects/blob/editor', ref: @ref + = render 'shared/commit_message_container', params: params, + placeholder: 'Add new file' + = hidden_field_tag 'content', '', id: 'file-content' + = render 'projects/commit_button', ref: @ref, + cancel_path: project_tree_path(@project, @id) + +:javascript + blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null) diff --git a/app/views/projects/edit_tree/preview.html.haml b/app/views/projects/blob/preview.html.haml similarity index 100% rename from app/views/projects/edit_tree/preview.html.haml rename to app/views/projects/blob/preview.html.haml diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 28de1a778a7..31bdbb562a1 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -51,15 +51,6 @@ = f.check_box :issues_enabled %span.descr Lightweight issue tracking system for this project - - if Project.issues_tracker.values.count > 1 - .form-group - = f.label :issues_tracker, "Issues tracker", class: 'control-label' - .col-sm-10= f.select(:issues_tracker, project_issues_trackers(@project.issues_tracker), {}, { disabled: !@project.issues_enabled }) - - .form-group - = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' - .col-sm-10= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?, class: 'form-control' - .form-group = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' .col-sm-10 @@ -89,8 +80,6 @@ .col-sm-10 - if @project.avatar? = project_icon(@project.to_param, alt: '', class: 'avatar s160') - - else - = project_icon(@project.to_param, alt: '', class: 'avatar s160', only_uploaded: true) %p.light - if @project.avatar_in_git Project avatar in repository: #{ @project.avatar_in_git } diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml deleted file mode 100644 index 7e0789853af..00000000000 --- a/app/views/projects/edit_tree/show.html.haml +++ /dev/null @@ -1,57 +0,0 @@ -.file-editor - %ul.nav.nav-tabs.js-edit-mode - %li.active - = link_to 'Edit', '#editor' - %li - = link_to editing_preview_title(@blob.name), '#preview', 'data-preview-url' => preview_project_edit_tree_path(@project, @id) - - = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do - = render 'projects/blob_editor', ref: @ref, path: @path, blob_data: @blob.data - = render 'shared/commit_message_container', params: params, - placeholder: "Update #{@blob.name}" - = 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] - = render 'projects/commit_button', ref: @ref, - cancel_path: @after_edit_path - -:javascript - ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace") - ace.config.loadModule("ace/ext/searchbox"); - var ace_mode = "#{@blob.language.try(:ace_mode)}"; - var editor = ace.edit("editor"); - if (ace_mode) { - editor.getSession().setMode('ace/mode/' + ace_mode); - } - - disableButtonIfEmptyField("#commit_message", ".js-commit-button"); - - $(".js-commit-button").click(function(){ - $("#file-content").val(editor.getValue()); - $(".file-editor form").submit(); - }); - - var editModePanes = $('.js-edit-mode-pane'), - editModeLinks = $('.js-edit-mode a'); - - editModeLinks.click(function(event) { - event.preventDefault(); - - var currentLink = $(this), - paneId = currentLink.attr('href'), - currentPane = editModePanes.filter(paneId); - - editModeLinks.parent().removeClass('active hover'); - currentLink.parent().addClass('active hover'); - editModePanes.hide(); - - if (paneId == '#preview') { - currentPane.fadeIn(200); - $.post(currentLink.data('preview-url'), { content: editor.getValue() }, function(response) { - currentPane.empty().append(response); - }) - } else { - currentPane.fadeIn(200); - editor.focus() - } - }) diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 2e46de6bfe0..36628195b4e 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -3,6 +3,17 @@ = render "home_panel" +.center.well + %h3 + The repository for this project is empty + %h4 + You can + = link_to project_new_blob_path(@project, 'master'), class: 'btn btn-new btn-lg' do + add a file +  or push it via command line. + +%h4 + %strong Command line instructions %div.git-empty %fieldset %legend Git global setup diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml index 54f2cef023b..959d5f08d47 100644 --- a/app/views/projects/forks/new.html.haml +++ b/app/views/projects/forks/new.html.haml @@ -1,5 +1,6 @@ %h3.page-title Fork project -%p.lead Select namespace where to fork this project +%p.lead + Click to fork the project to a user or group %hr .fork-namespaces diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index 11a111e5faa..f8ee6973637 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -45,10 +45,17 @@ .automerge_widget.cannot_be_merged.hide %h4 This request can't be merged with GitLab. - %p You should do it manually with %strong - = link_to "command line", "#modal_merge_info", class: "how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" + = link_to "#modal_merge_info", class: "underlined-link how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" do + command line + + %p + %button.btn.disabled + %i.fa.fa-warning + Accept Merge Request +   + This usually happens when git can not resolve conflicts between branches automatically. .automerge_widget.unchecked %p diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml deleted file mode 100644 index cf7b768694f..00000000000 --- a/app/views/projects/new_tree/show.html.haml +++ /dev/null @@ -1,38 +0,0 @@ -%h3.page-title New file -%hr -.file-editor - = form_tag(project_new_tree_path(@project, @id), method: :put, class: 'form-horizontal form-new-file') do - .form-group.commit_message-group - = label_tag 'file_name', class: 'control-label' do - File name - .col-sm-10 - .input-group - %span.input-group-addon - = @path[-1] == "/" ? @path : @path + "/" - = text_field_tag 'file_name', params[:file_name], placeholder: "sample.rb", required: true, class: 'form-control' - %span.input-group-addon - on - %span= @ref - - .form-group.commit_message-group - = label_tag :encoding, class: "control-label" do - Encoding - .col-sm-10 - = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' - = render 'projects/blob_editor', ref: @ref - = render 'shared/commit_message_container', params: params, - placeholder: 'Add new file' - = hidden_field_tag 'content', '', id: 'file-content' - = render 'projects/commit_button', ref: @ref, - cancel_path: project_tree_path(@project, @id) - -:javascript - ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") - var editor = ace.edit("editor"); - - disableButtonIfAnyEmptyField($('.form-new-file'), '.form-control', '.btn-create') - - $(".js-commit-button").click(function(){ - $("#file-content").val(editor.getValue()); - $(".file-editor form").submit(); - }); diff --git a/app/views/projects/team_members/_form.html.haml b/app/views/projects/team_members/_form.html.haml index 2bf61fa12bb..ddf8cb76f78 100644 --- a/app/views/projects/team_members/_form.html.haml +++ b/app/views/projects/team_members/_form.html.haml @@ -17,7 +17,12 @@ %p 2. Set access level for them .form-group = f.label :access_level, "Project Access", class: 'control-label' - .col-sm-10= select_tag :access_level, options_for_select(Gitlab::Access.options, @user_project_relation.access_level), class: "project-access-select select2" + .col-sm-10 + = select_tag :access_level, options_for_select(Gitlab::Access.options, @user_project_relation.access_level), class: "project-access-select select2" + .help-block + Read more about role permissions + %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + .form-actions = f.submit 'Add users', class: "btn btn-create" diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml index 68ccd4d61bb..f902440b3f1 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -10,7 +10,7 @@ = link_to title, '#' - if current_user && can_push_branch?(@project, @ref) %li - = link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do + = link_to project_new_blob_path(@project, @id), title: 'New file', id: 'new-file-link' do %small %i.fa.fa-plus diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml new file mode 100644 index 00000000000..727faf23679 --- /dev/null +++ b/app/views/users/calendar.html.haml @@ -0,0 +1,8 @@ +%h4 Calendar: +#cal-heatmap.calendar + :javascript + new calendar( + #{@timestamps.to_json}, + #{@starting_year}, + #{@starting_month} + ); diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 54f2666ce5d..445f43cd500 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -18,6 +18,11 @@ %h4 Groups: = render 'groups', groups: @groups %hr + + .user-calendar + %h4.center.light + %i.fa.fa-spinner.fa-spin + %hr %h4 User Activity: @@ -32,3 +37,8 @@ = render 'profile', user: @user - if @projects.present? = render 'projects', projects: @projects + + +:coffeescript + $ -> + $(".user-calendar").load("#{user_calendar_path}") diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index e5780cabb63..59af49c0180 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -153,9 +153,9 @@ production: &base label: 'LDAP' host: '_your_ldap_server' - port: 636 + port: 389 uid: 'sAMAccountName' - method: 'ssl' # "tls" or "ssl" or "plain" + method: 'plain' # "tls" or "ssl" or "plain" bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' password: '_the_password_of_the_bind_user' diff --git a/config/routes.rb b/config/routes.rb index 8c3eef23260..e122777314a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -157,6 +157,9 @@ Gitlab::Application.routes.draw do end end + get 'u/:username/calendar' => 'users#calendar', as: :user_calendar, + constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } + get '/u/:username' => 'users#show', as: :user, constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } @@ -211,17 +214,20 @@ Gitlab::Application.routes.draw do end scope module: :projects do + # Blob routes: + get '/new/:id', to: 'blob#new', constraints: {id: /.+/}, as: 'new_blob' + post '/create/:id', to: 'blob#create', constraints: {id: /.+/}, as: 'create_blob' + get '/edit/:id', to: 'blob#edit', constraints: {id: /.+/}, as: 'edit_blob' + put '/update/:id', to: 'blob#update', constraints: {id: /.+/}, as: 'update_blob' + post '/preview/:id', to: 'blob#preview', constraints: {id: /.+/}, as: 'preview_blob' + resources :blob, only: [:show, :destroy], constraints: { id: /.+/, format: false } do get :diff, on: :member end + resources :raw, only: [:show], constraints: {id: /.+/} resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } - resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do - # Cannot be GET to differentiate from GET paths that end in preview. - post :preview, on: :member - end resource :avatar, only: [:show, :destroy] - resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new' resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} resources :compare, only: [:index, :create] diff --git a/db/migrate/20150116234545_add_gitlab_access_token_to_user.rb b/db/migrate/20150116234545_add_gitlab_access_token_to_user.rb new file mode 100644 index 00000000000..c28ba3197ac --- /dev/null +++ b/db/migrate/20150116234545_add_gitlab_access_token_to_user.rb @@ -0,0 +1,5 @@ +class AddGitlabAccessTokenToUser < ActiveRecord::Migration + def change + add_column :users, :gitlab_access_token, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 32e49ff7a74..0e4af3df7c2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -435,6 +435,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do 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" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/doc/api/users.md b/doc/api/users.md index b30a31deccc..71fa62bdd65 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -322,6 +322,31 @@ Parameters: - `title` (required) - new SSH Key's title - `key` (required) - new SSH key +```json +{ + "created_at": "2015-01-21T17:44:33.512Z", + "key": "ssh-dss AAAAB3NzaC1kc3MAAACBAMLrhYgI3atfrSD6KDas1b/3n6R/HP+bLaHHX6oh+L1vg31mdUqK0Ac/NjZoQunavoyzqdPYhFz9zzOezCrZKjuJDS3NRK9rspvjgM0xYR4d47oNZbdZbwkI4cTv/gcMlquRy0OvpfIvJtjtaJWMwTLtM5VhRusRuUlpH99UUVeXAAAAFQCVyX+92hBEjInEKL0v13c/egDCTQAAAIEAvFdWGq0ccOPbw4f/F8LpZqvWDydAcpXHV3thwb7WkFfppvm4SZte0zds1FJ+Hr8Xzzc5zMHe6J4Nlay/rP4ewmIW7iFKNBEYb/yWa+ceLrs+TfR672TaAgO6o7iSRofEq5YLdwgrwkMmIawa21FrZ2D9SPao/IwvENzk/xcHu7YAAACAQFXQH6HQnxOrw4dqf0NqeKy1tfIPxYYUZhPJfo9O0AmBW2S36pD2l14kS89fvz6Y1g8gN/FwFnRncMzlLY/hX70FSc/3hKBSbH6C6j8hwlgFKfizav21eS358JJz93leOakJZnGb8XlWvz1UJbwCsnR2VEY8Dz90uIk1l/UqHkA= loic@call", + "title": "ABC", + "id": 4 +} +``` + +Will return created key with status `201 Created` on success. If an +error occurs a `400 Bad Request` is returned with a message explaining the error: + +```json +{ + "message": { + "fingerprint": [ + "has already been taken" + ], + "key": [ + "has already been taken" + ] + } +} +``` + ## Add SSH key for user Create new key owned by specified user. Available only for admin diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index 56b0d826adb..125ce31b521 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -29,9 +29,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server label: 'LDAP' host: '_your_ldap_server' - port: 636 + port: 389 uid: 'sAMAccountName' - method: 'ssl' # "tls" or "ssl" or "plain" + method: 'plain' # "tls" or "ssl" or "plain" bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' password: '_the_password_of_the_bind_user' @@ -76,6 +76,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server EOS ``` +If you are getting 'Connection Refused' errors when trying to connect to the LDAP server please double-check the LDAP `port` and `method` settings used by GitLab. +Common combinations are `method: 'plain'` and `port: 389`, OR `method: 'ssl'` and `port: 636`. + If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`: ``` diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index 54e6e3a9e3f..41c2732ef77 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -1,6 +1,6 @@ # System hooks -Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create` and `key_destroy`. +Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`. System hooks can be used, e.g. for logging or changing information in a LDAP server. @@ -50,6 +50,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser "project_path": "storecloud", "user_email": "johnsmith@gmail.com", "user_name": "John Smith", + "user_id": 41, "project_visibility": "private", } ``` @@ -66,6 +67,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser "project_path": "storecloud", "user_email": "johnsmith@gmail.com", "user_name": "John Smith", + "user_id": 41, "project_visibility": "private", } ``` @@ -117,3 +119,62 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser "id": 4 } ``` + +**Group created:** + +```json +{ + "created_at": "2012-07-21T07:30:54Z", + "event_name": "group_create", + "name": "StormCloud", + "owner_email": "johnsmith@gmail.com", + "owner_name": "John Smith", + "path": "stormcloud", + "group_id": 78 +} +``` + +**Group removed:** + +```json +{ + "created_at": "2012-07-21T07:30:54Z", + "event_name": "group_destroy", + "name": "StoreCloud", + "owner_email": "johnsmith@gmail.com", + "owner_name": "John Smith", + "path": "storecloud", + "group_id": 78 +} +``` + +**New Group Member:** + +```json +{ + "created_at": "2012-07-21T07:30:56Z", + "event_name": "user_add_to_group", + "group_access": "Master", + "group_id": 78, + "group_name": "StoreCloud", + "group_path": "storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_id": 41 +} +``` +**Group Member Removed:** + +```json +{ + "created_at": "2012-07-21T07:30:56Z", + "event_name": "user_remove_from_group", + "group_access": "Master", + "group_id": 78, + "group_name": "StoreCloud", + "group_path": "storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_id": 41 +} +``` diff --git a/docker/Dockerfile b/docker/Dockerfile index 445fdd6d063..70d6c721f1c 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.6.2-omnibus.5.3.0.ci.1-1_amd64.deb \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.7.1-omnibus.5.4.1.ci-1_amd64.deb \ && dpkg -i $TMP_FILE \ && rm -f $TMP_FILE diff --git a/features/project/edit_issuetracker.feature b/features/project/edit_issuetracker.feature deleted file mode 100644 index cc0de07ca69..00000000000 --- a/features/project/edit_issuetracker.feature +++ /dev/null @@ -1,18 +0,0 @@ -Feature: Project Issue Tracker - Background: - Given I sign in as a user - And I own project "Shop" - And project "Shop" has issues enabled - And I visit project "Shop" page - - Scenario: I set the issue tracker to "GitLab" - When I visit edit project "Shop" page - And change the issue tracker to "GitLab" - And I save project - Then I the project should have "GitLab" as issue tracker - - Scenario: I set the issue tracker to "Redmine" - When I visit edit project "Shop" page - And change the issue tracker to "Redmine" - And I save project - Then I the project should have "Redmine" as issue tracker diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 6ea64f70092..ccb29293a89 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -34,6 +34,19 @@ 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 file in empty repo + Given I own an empty project + And I visit my empty project page + And I create bare repo + When I click on "add a file" link + And I edit code + And I fill the new file name + And I fill the commit message + And I click on "Commit Changes" + Then I am redirected to the new file + And I should see its new content + @javascript Scenario: If I enter an illegal file name I see an error message Given I click on "new file" link in repo diff --git a/features/steps/project/issue_tracker.rb b/features/steps/project/issue_tracker.rb deleted file mode 100644 index e1700292701..00000000000 --- a/features/steps/project/issue_tracker.rb +++ /dev/null @@ -1,31 +0,0 @@ -class Spinach::Features::ProjectIssueTracker < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedPaths - - step 'project "Shop" has issues enabled' do - @project = Project.find_by(name: "Shop") - @project ||= create(:project, name: "Shop", namespace: @user.namespace) - @project.issues_enabled = true - end - - step 'change the issue tracker to "GitLab"' do - select 'GitLab', from: 'project_issues_tracker' - end - - step 'I the project should have "GitLab" as issue tracker' do - find_field('project_issues_tracker').value.should == 'gitlab' - end - - step 'change the issue tracker to "Redmine"' do - select 'Redmine', from: 'project_issues_tracker' - end - - step 'I the project should have "Redmine" as issue tracker' do - find_field('project_issues_tracker').value.should == 'redmine' - end - - step 'I save project' do - click_button 'Save changes' - end -end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 071ef75dc62..6f421de1aba 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -173,7 +173,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps merge!: true, ) - click_button "Accept Merge Request" + within '.can_be_merged' do + click_button "Accept Merge Request" + end end step 'I should see merged request' do diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 805e6ff0eac..770e8162497 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -58,7 +58,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step 'I can edit code' do set_new_content - evaluate_script('editor.getValue()').should == new_gitignore_content + evaluate_script('blob.editor.getValue()').should == new_gitignore_content end step 'I edit code' do @@ -78,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I click link "Diff"' do - click_link 'Diff' + click_link 'Preview changes' end step 'I click on "Commit Changes"' do @@ -103,7 +103,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step 'I can see new file page' do page.should have_content "New file" - page.should have_content "File name" page.should have_content "Commit message" end @@ -167,10 +166,21 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(page).to have_content('Your changes could not be committed') end + step 'I create bare repo' do + click_link 'Create empty bare repository' + end + + step 'I click on "add a file" link' do + click_link 'add a file' + + # Remove pre-receive hook so we can push without auth + FileUtils.rm(File.join(Project.last.repository.path, 'hooks', 'pre-receive')) + end + private def set_new_content - execute_script("editor.setValue('#{new_gitignore_content}')") + execute_script("blob.editor.setValue('#{new_gitignore_content}')") end # Content of the gitignore file on the seed repository. diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 33ef6ccacf1..cef48c179b2 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -284,11 +284,11 @@ module SharedPaths end step 'I am on the new file page' do - current_path.should eq(project_new_tree_path(@project, root_ref)) + current_path.should eq(project_create_blob_path(@project, root_ref)) end step 'I am on the ".gitignore" edit file page' do - current_path.should eq(project_edit_tree_path( + current_path.should eq(project_edit_blob_path( @project, File.join(root_ref, '.gitignore'))) end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 0bd5653538c..cf0be256231 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -28,6 +28,10 @@ module SharedProject @project.team << [@user, :master] end + step 'I visit my empty project page' do + visit project_path(Project.find_by(name: 'Empty Project')) + end + step 'project "Shop" has push event' do @project = Project.find_by(name: "Shop") diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 03a556a2c55..b259914a01c 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -58,11 +58,13 @@ module API # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used # Example Request: # GET /projects/:id/repository/tree - get ":id/repository/tree" do + get ':id/repository/tree' do ref = params[:ref_name] || user_project.try(:default_branch) || 'master' path = params[:path] || nil commit = user_project.repository.commit(ref) + not_found!('Tree') unless commit + tree = user_project.repository.tree(commit.id, path) present tree.sorted_entries, with: Entities::RepoTreeObject @@ -100,14 +102,18 @@ module API # sha (required) - The blob's sha # Example Request: # GET /projects/:id/repository/raw_blobs/:sha - get ":id/repository/raw_blobs/:sha" do + get ':id/repository/raw_blobs/:sha' do ref = params[:sha] repo = user_project.repository - blob = Gitlab::Git::Blob.raw(repo, ref) + begin + blob = Gitlab::Git::Blob.raw(repo, ref) + rescue + not_found! 'Blob' + end - not_found! "Blob" unless blob + not_found! 'Blob' unless blob env['api.format'] = :txt @@ -122,13 +128,23 @@ module API # sha (optional) - the commit sha to download defaults to the tip of the default branch # Example Request: # GET /projects/:id/repository/archive - get ":id/repository/archive", requirements: { format: Gitlab::Regex.archive_formats_regex } do + get ':id/repository/archive', + requirements: { format: Gitlab::Regex.archive_formats_regex } do authorize! :download_code, user_project - file_path = ArchiveRepositoryService.new.execute(user_project, params[:sha], params[:format]) + + begin + file_path = ArchiveRepositoryService.new.execute( + user_project, + params[:sha], + params[:format]) + rescue + not_found!('File') + end if file_path && File.exists?(file_path) data = File.open(file_path, 'rb').read - header["Content-Disposition"] = "attachment; filename=\"#{File.basename(file_path)}\"" + basename = File.basename(file_path) + header['Content-Disposition'] = "attachment; filename=\"#{basename}\"" content_type MIME::Types.type_for(file_path).first.content_type env['api.format'] = :binary present data @@ -161,7 +177,12 @@ module API get ':id/repository/contributors' do authorize! :download_code, user_project - present user_project.repository.contributors, with: Entities::Contributor + begin + present user_project.repository.contributors, + with: Entities::Contributor + rescue + not_found! + end end end end diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index e51cb30bdd9..19215cfb7e6 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -1,17 +1,9 @@ # Module providing methods for dealing with separating a tree-ish string and a # file path string when combined in a request parameter module ExtractsPath - extend ActiveSupport::Concern - # Raised when given an invalid file path class InvalidPathError < StandardError; end - included do - if respond_to?(:before_filter) - before_filter :assign_ref_vars - end - end - # Given a string containing both a Git tree-ish, such as a branch or tag, and # a filesystem path joined by forward slashes, attempts to separate the two. # diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 1f71906bc8e..2e393f753e8 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -34,7 +34,7 @@ module Grack def auth! if @auth.provided? return bad_request unless @auth.basic? - + # Authentication with username and password login, password = @auth.credentials @@ -71,8 +71,20 @@ module Grack false end + def oauth_access_token_check(login, password) + if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present? + token = Doorkeeper::AccessToken.by_token(password) + token && token.accessible? && User.find_by(id: token.resource_owner_id) + end + end + def authenticate_user(login, password) user = Gitlab::Auth.new.find(login, password) + + unless user + user = oauth_access_token_check(login, password) + end + return user if user.present? # At this point, we know the credentials were wrong. We let Rack::Attack diff --git a/lib/gitlab/commits_calendar.rb b/lib/gitlab/commits_calendar.rb new file mode 100644 index 00000000000..ccc80d080af --- /dev/null +++ b/lib/gitlab/commits_calendar.rb @@ -0,0 +1,25 @@ +module Gitlab + class CommitsCalendar + attr_reader :timestamps + + def initialize(repositories, user) + @timestamps = {} + date_timestamps = [] + + repositories.select(&:exists?).reject(&:empty?).each do |raw_repository| + commits_log = raw_repository.commits_per_day_for_user(user) + date_timestamps << commits_log + end + + date_timestamps = date_timestamps.inject do |collection, date| + collection.merge(date) { |k, old_v, new_v| old_v + new_v } + end + + date_timestamps ||= [] + date_timestamps.each do |date, commits| + timestamp = Date.parse(date).to_time.to_i.to_s rescue nil + @timestamps[timestamp] = commits if timestamp + end + end + end +end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index c7bf2efc628..ea96d04c5ab 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -73,7 +73,7 @@ module Gitlab changes = changes.lines if changes.kind_of?(String) # Iterate over all changes to find if user allowed all of them to be applied - changes.each do |change| + changes.map(&:strip).reject(&:blank?).each do |change| status = change_access_check(user, project, change) unless status.allowed? # If user does not have access to make at least one change - cancel all push diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 3ef494ba137..cfa8692659d 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -40,12 +40,16 @@ module Gitlab def update_user_attributes gl_user.email = auth_hash.email - gl_user.identities.build(provider: auth_hash.provider, extern_uid: auth_hash.uid) + + # Build new identity only if we dont have have same one + gl_user.identities.find_or_initialize_by(provider: auth_hash.provider, + extern_uid: auth_hash.uid) + gl_user end def changed? - gl_user.changed? + gl_user.changed? || gl_user.identities.any?(&:changed?) end def needs_blocking? diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 068c342398b..c0e83fb3078 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -208,7 +208,7 @@ module Gitlab end def reference_issue(identifier, project = @project, prefix_text = nil) - if project.used_default_issues_tracker? || !external_issues_tracker_enabled? + if project.default_issues_tracker? if project.issue_exists? identifier url = url_for_issue(identifier, project) title = title_for_issue(identifier, project) @@ -220,10 +220,8 @@ module Gitlab link_to("#{prefix_text}##{identifier}", url, options) end else - config = Gitlab.config - external_issue_tracker = config.issues_tracker[project.issues_tracker] - if external_issue_tracker.present? - reference_external_issue(identifier, external_issue_tracker, project, + if project.external_issue_tracker.present? + reference_external_issue(identifier, project, prefix_text) end end @@ -267,10 +265,10 @@ module Gitlab end end - def reference_external_issue(identifier, issue_tracker, project = @project, + def reference_external_issue(identifier, project = @project, prefix_text = nil) url = url_for_issue(identifier, project) - title = issue_tracker['title'] + title = project.external_issue_tracker.title options = html_options.merge( title: "Issue in #{title}", diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb index 15e9b7a6f77..5b657c7aba2 100644 --- a/lib/gitlab/satellite/files/new_file_action.rb +++ b/lib/gitlab/satellite/files/new_file_action.rb @@ -14,7 +14,14 @@ module Gitlab prepare_satellite!(repo) # create target branch in satellite at the corresponding commit from bare repo - repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") + current_ref = + if @project.empty_repo? + # skip this step if we want to add first file to empty repo + Satellite::PARKING_BRANCH + else + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") + ref + end file_path_in_satellite = File.join(repo.working_dir, file_path) dir_name_in_satellite = File.dirname(file_path_in_satellite) @@ -38,10 +45,9 @@ module Gitlab # will raise CommandFailed when commit fails repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) - # push commit back to bare repo # will raise CommandFailed when push fails - repo.git.push({raise: true, timeout: true}, :origin, ref) + repo.git.push({raise: true, timeout: true}, :origin, "#{current_ref}:#{ref}") # everything worked true diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb new file mode 100644 index 00000000000..44225c054f2 --- /dev/null +++ b/spec/controllers/users_controller_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe UsersController do + let(:user) { create(:user, username: "user1", name: "User 1", email: "user1@gitlab.com") } + + before do + sign_in(user) + end + + describe "GET #show" do + render_views + + it "renders the show template" do + get :show, username: user.username + expect(response.status).to eq(200) + expect(response).to render_template("show") + end + end + + describe "GET #calendar" do + it "renders calendar" do + get :calendar, username: user.username + expect(response).to render_template("calendar") + end + end +end + diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 1738b20fab2..5ae57718c1a 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -76,7 +76,19 @@ FactoryGirl.define do end factory :redmine_project, parent: :project do - issues_tracker { "redmine" } - issues_tracker_id { "project_name_in_redmine" } + after :create do |project| + project.create_redmine_service( + active: true, + properties: { + 'project_url' => 'http://redmine/projects/project_name_in_redmine', + 'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id", + 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new' + } + ) + end + after :create do |project| + project.issues_tracker = 'redmine' + project.issues_tracker_id = 'project_name_in_redmine' + end end end diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb index a7f87906b2d..52ade3e2d31 100644 --- a/spec/features/atom/dashboard_spec.rb +++ b/spec/features/atom/dashboard_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe "Dashboard Feed", feature: true do describe "GET /" do - let!(:user) { create(:user) } + let!(:user) { create(:user, name: "Jonh") } context "projects atom feed via private token" do it "should render projects atom feed" do diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 86ba801ce07..d633287b2a9 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -23,6 +23,7 @@ describe GitlabMarkdownHelper do @project = project @ref = 'markdown' @repository = project.repository + @request.host = Gitlab.config.gitlab.host end describe "#gfm" do @@ -296,10 +297,13 @@ describe GitlabMarkdownHelper do let(:reference) { "JIRA-#{issue.iid}" } before do - issue_tracker_config = { "jira" => { "title" => "JIRA tracker", "issues_url" => "http://jira.example/browse/:id" } } - Gitlab.config.stub(:issues_tracker).and_return(issue_tracker_config) - @project.stub(:issues_tracker).and_return("jira") - @project.stub(:issues_tracker_id).and_return("JIRA") + jira = @project.create_jira_service if @project.jira_service.nil? + properties = {"title"=>"JIRA tracker", "project_url"=>"http://jira.example/issues/?jql=project=A", "issues_url"=>"http://jira.example/browse/:id", "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa"} + jira.update_attributes(properties: properties, active: true) + end + + after do + @project.jira_service.destroy! unless @project.jira_service.nil? end it "should link using a valid id" do diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 9c95bc044f3..c82729a52e2 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -24,7 +24,7 @@ describe IssuesHelper do end describe :url_for_project_issues do - let(:project_url) { Gitlab.config.issues_tracker.redmine.project_url} + let(:project_url) { ext_project.external_issue_tracker.project_url } let(:ext_expected) do project_url.gsub(':project_id', ext_project.id.to_s) .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) @@ -54,17 +54,16 @@ describe IssuesHelper do Gitlab.config.stub(:issues_tracker).and_return(nil) end - it "should return path to internal tracker" do - url_for_project_issues.should match(polymorphic_path([@project])) + it "should return path to external tracker" do + url_for_project_issues.should match(ext_expected) end end end describe :url_for_issue do - let(:issue_id) { 3 } - let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url} + let(:issues_url) { ext_project.external_issue_tracker.issues_url} let(:ext_expected) do - issues_url.gsub(':id', issue_id.to_s) + issues_url.gsub(':id', issue.iid.to_s) .gsub(':project_id', ext_project.id.to_s) .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) end @@ -78,7 +77,7 @@ describe IssuesHelper do it "should return path to external tracker" do @project = ext_project - url_for_issue(issue_id).should match(ext_expected) + url_for_issue(issue.iid).should match(ext_expected) end it "should return empty string if project nil" do @@ -93,14 +92,14 @@ describe IssuesHelper do Gitlab.config.stub(:issues_tracker).and_return(nil) end - it "should return internal path" do - url_for_issue(issue.iid).should match(polymorphic_path([@project, issue])) + it "should return external path" do + url_for_issue(issue.iid).should match(ext_expected) end end end describe :url_for_new_issue do - let(:issues_url) { Gitlab.config.issues_tracker.redmine.new_issue_url} + let(:issues_url) { ext_project.external_issue_tracker.new_issue_url } let(:ext_expected) do issues_url.gsub(':project_id', ext_project.id.to_s) .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) @@ -131,7 +130,7 @@ describe IssuesHelper do end it "should return internal path" do - url_for_new_issue.should match(new_project_issue_path(@project)) + url_for_new_issue.should match(ext_expected) end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 2146b0b1383..281d4862199 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -1,32 +1,11 @@ require 'spec_helper' describe ProjectsHelper do - describe '#project_issues_trackers' do - it "returns the correct issues trackers available" do - project_issues_trackers.should == - "\n" \ - "" - end - - it "returns the correct issues trackers available with current tracker 'gitlab' selected" do - project_issues_trackers('gitlab').should == - "\n" \ - "" - end - - it "returns the correct issues trackers available with current tracker 'redmine' selected" do - project_issues_trackers('redmine').should == - "\n" \ - "" - end - end - describe "#project_status_css_class" do it "returns appropriate class" do project_status_css_class("started").should == "active" project_status_css_class("failed").should == "danger" project_status_css_class("finished").should == "success" end - end end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index f73884e6441..63ffc21ba3b 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -13,6 +13,23 @@ describe Gitlab::LDAP::User do double(uid: 'my-uid', provider: 'ldapmain', info: double(info)) end + describe :changed? do + it "marks existing ldap user as changed" do + existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') + expect(gl_user.changed?).to be_true + end + + it "marks existing non-ldap user if the email matches as changed" do + existing_user = create(:user, email: 'john@example.com') + expect(gl_user.changed?).to be_true + end + + it "dont marks existing ldap user as changed" do + existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain') + expect(gl_user.changed?).to be_false + end + end + describe :find_or_create do it "finds the user if already existing" do existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 23867df39dd..5f45df4e8c3 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -12,7 +12,6 @@ describe Gitlab::ReferenceExtractor do end it 'extracts JIRA issue references' do - Gitlab.config.gitlab.stub(:issues_tracker).and_return('jira') subject.analyze('this one talks about issue JIRA-1234', nil) subject.issues.should == [{ project: nil, id: 'JIRA-1234' }] end diff --git a/spec/models/jira_service_spec.rb b/spec/models/jira_service_spec.rb new file mode 100644 index 00000000000..0c73a68c924 --- /dev/null +++ b/spec/models/jira_service_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe JiraService do + describe "Associations" do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe "Validations" do + context "active" do + before do + subject.active = true + end + + it { should validate_presence_of :project_url } + it { should validate_presence_of :issues_url } + it { should validate_presence_of :new_issue_url } + end + end + + describe 'description and title' do + let(:project) { create(:project) } + + context 'when it is not set' do + before do + @service = project.create_jira_service(active: true) + end + + after do + @service.destroy! + end + + it 'should be initialized' do + expect(@service.title).to eq('JIRA') + expect(@service.description).to eq("Jira issue tracker") + end + end + + context 'when it is set' do + before do + properties = { 'title' => 'Jira One', 'description' => 'Jira One issue tracker' } + @service = project.create_jira_service(active: true, properties: properties) + end + + after do + @service.destroy! + end + + it "should be correct" do + expect(@service.title).to eq('Jira One') + expect(@service.description).to eq('Jira One issue tracker') + end + end + end + + describe 'project and issue urls' do + let(:project) { create(:project) } + + context 'when gitlab.yml was initialized' do + before do + settings = { "jira" => { + "title" => "Jira", + "project_url" => "http://jira.sample/projects/project_a", + "issues_url" => "http://jira.sample/issues/:id", + "new_issue_url" => "http://jira.sample/projects/project_a/issues/new" + } + } + Gitlab.config.stub(:issues_tracker).and_return(settings) + @service = project.create_jira_service(active: true) + end + + after do + @service.destroy! + end + + it 'should be prepopulated with the settings' do + expect(@service.properties[:project_url]).to eq('http://jira.sample/projects/project_a') + expect(@service.properties[:issues_url]).to eq("http://jira.sample/issues/:id") + expect(@service.properties[:new_issue_url]).to eq("http://jira.sample/projects/project_a/issues/new") + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c9bdeb43f6f..092c02d552e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -198,16 +198,16 @@ describe Project do end end - describe :used_default_issues_tracker? do + describe :default_issues_tracker? do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } - it 'should be true if used internal tracker' do - project.used_default_issues_tracker?.should be_true + it "should be true if used internal tracker" do + project.default_issues_tracker?.should be_true end - it 'should be false if used other tracker' do - ext_project.used_default_issues_tracker?.should be_false + it "should be false if used other tracker" do + ext_project.default_issues_tracker?.should be_false end end diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb index 4ab5261dc9d..8deb732de9c 100644 --- a/spec/models/system_hook_spec.rb +++ b/spec/models/system_hook_spec.rb @@ -61,5 +61,40 @@ describe SystemHook do project.project_members.destroy_all WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once end + + it 'group create hook' do + create(:group) + WebMock.should have_requested(:post, @system_hook.url).with( + body: /group_create/ + ).once + end + + it 'group destroy hook' do + group = create(:group) + group.destroy + WebMock.should have_requested(:post, @system_hook.url).with( + body: /group_destroy/ + ).once + end + + it 'group member create hook' do + group = create(:group) + user = create(:user) + group.add_user(user, Gitlab::Access::MASTER) + WebMock.should have_requested(:post, @system_hook.url).with( + body: /user_add_to_group/ + ).once + end + + it 'group member destroy hook' do + group = create(:group) + user = create(:user) + group.add_user(user, Gitlab::Access::MASTER) + group.group_members.destroy_all + WebMock.should have_requested(:post, @system_hook.url).with( + body: /user_remove_from_group/ + ).once + end + end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index beae71c02d9..5518d2df566 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -101,6 +101,14 @@ describe API::API, api: true do json_response.first['type'].should == 'tree' json_response.first['mode'].should == '040000' end + + it 'should return a 404 for unknown ref' do + get api("/projects/#{project.id}/repository/tree?ref_name=foo", user) + response.status.should == 404 + + json_response.should be_an Object + json_response['message'] == '404 Tree Not Found' + end end context "unauthorized user" do @@ -145,6 +153,14 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user) response.status.should == 200 end + + it 'should return a 404 for unknown blob' do + get api("/projects/#{project.id}/repository/raw_blobs/123456", user) + response.status.should == 404 + + json_response.should be_an Object + json_response['message'] == '404 Blob Not Found' + end end describe "GET /projects/:id/repository/archive(.:format)?:sha" do diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 8191d1fb9c4..e36b266a1ff 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -430,21 +430,17 @@ describe Projects::TreeController, 'routing' do end end -describe Projects::EditTreeController, 'routing' do - it 'to #show' do +describe Projects::BlobController, 'routing' do + it 'to #edit' do get('/gitlab/gitlabhq/edit/master/app/models/project.rb').should( - route_to('projects/edit_tree#show', + route_to('projects/blob#edit', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')) - get('/gitlab/gitlabhq/edit/master/app/models/project.rb/preview').should( - route_to('projects/edit_tree#show', - project_id: 'gitlab/gitlabhq', - id: 'master/app/models/project.rb/preview')) end it 'to #preview' do - post('/gitlab/gitlabhq/edit/master/app/models/project.rb/preview').should( - route_to('projects/edit_tree#preview', + post('/gitlab/gitlabhq/preview/master/app/models/project.rb').should( + route_to('projects/blob#preview', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')) end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 347560414e7..36030577835 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -22,6 +22,7 @@ describe Issues::UpdateService do } @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + @issue.reload end it { @issue.should be_valid } diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index c8f40f48bab..0e60baae2c4 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -21,12 +21,14 @@ describe MergeRequests::UpdateService do state_event: 'close' } end + let(:service) { MergeRequests::UpdateService.new(project, user, opts) } before do service.stub(:execute_hooks) @merge_request = service.execute(merge_request) + @merge_request.reload end it { @merge_request.should be_valid } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index e305536f7ee..2ba1e3372b9 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -187,7 +187,7 @@ describe NotificationService do end describe 'Issues' do - let(:issue) { create :issue, assignee: create(:user) } + let(:issue) { create :issue, assignee: create(:user), description: 'cc @participant' } before do build_team(issue.project) @@ -197,6 +197,7 @@ describe NotificationService do it do should_email(issue.assignee_id) should_email(@u_watcher.id) + should_email(@u_participant_mentioned.id) should_not_email(@u_mentioned.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) @@ -222,6 +223,7 @@ describe NotificationService do it 'should email new assignee' do should_email(issue.assignee_id) should_email(@u_watcher.id) + should_email(@u_participant_mentioned.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) @@ -242,6 +244,7 @@ describe NotificationService do should_email(issue.assignee_id) should_email(issue.author_id) should_email(@u_watcher.id) + should_email(@u_participant_mentioned.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) @@ -262,6 +265,7 @@ describe NotificationService do should_email(issue.assignee_id) should_email(issue.author_id) should_email(@u_watcher.id) + should_email(@u_participant_mentioned.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) @@ -404,6 +408,7 @@ describe NotificationService do def build_team(project) @u_watcher = create(:user, notification_level: Notification::N_WATCH) @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) + @u_participant_mentioned = create(:user, username: 'participant', notification_level: Notification::N_PARTICIPATING) @u_disabled = create(:user, notification_level: Notification::N_DISABLED) @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION) @u_committer = create(:user, username: 'committer') diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 573446d3a19..a45e9d0575c 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -5,6 +5,8 @@ describe SystemHooksService do let (:project) { create :project } let (:project_member) { create :project_member } let (:key) { create(:key, user: user) } + let (:group) { create(:group) } + let (:group_member) { create(:group_member) } context 'event data' do it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) } @@ -15,6 +17,31 @@ describe SystemHooksService do it { event_data(project_member, :destroy).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) } it { event_data(key, :create).should include(:username, :key, :id) } it { event_data(key, :destroy).should include(:username, :key, :id) } + + it do + event_data(group, :create).should include( + :event_name, :name, :created_at, :path, :group_id, :owner_name, + :owner_email + ) + end + it do + event_data(group, :destroy).should include( + :event_name, :name, :created_at, :path, :group_id, :owner_name, + :owner_email + ) + end + it do + event_data(group_member, :create).should include( + :event_name, :created_at, :group_name, :group_path, :group_id, :user_id, + :user_name, :user_email, :group_access + ) + end + it do + event_data(group_member, :destroy).should include( + :event_name, :created_at, :group_name, :group_path, :group_id, :user_id, + :user_name, :user_email, :group_access + ) + end end context 'event names' do @@ -26,6 +53,10 @@ describe SystemHooksService do it { event_name(project_member, :destroy).should eq "user_remove_from_team" } it { event_name(key, :create).should eq 'key_create' } it { event_name(key, :destroy).should eq 'key_destroy' } + it { event_name(group, :create).should eq 'group_create' } + it { event_name(group, :destroy).should eq 'group_destroy' } + it { event_name(group_member, :create).should eq 'user_add_to_group' } + it { event_name(group_member, :destroy).should eq 'user_remove_from_group' } end def event_data(*args)