diff options
281 files changed, 3436 insertions, 1443 deletions
diff --git a/CHANGELOG b/CHANGELOG index f1346885ab4..2db5beb0022 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,40 +3,43 @@ 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) - - - + - Allow more variations for commit messages closing issues (Julien Bianchi and Hannes Rosenögger) - - + - 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 + - Show success/error messages for test setting button in services - - Fix commits pagination - - - - + - 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) - - - - - - - - - - + - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation. - - - @@ -47,16 +50,27 @@ v 7.8.0 - - - + - Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger) - - - - - - + - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) - - - - - + - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) + +v 7.7.2 + - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch + - Fix issue when LDAP user can't login with existing GitLab account + +v 7.7.1 + - Improve mention autocomplete performance + - Show setup instructions for GitHub import if disabled + - Allow use http for OAuth applications v 7.7.0 - Import from GitHub.com feature diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 005119baaa0..8e8299dcc06 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.4.1 +2.4.2 @@ -118,7 +118,7 @@ gem "acts-as-taggable-on" # Background jobs gem 'slim' gem 'sinatra', require: nil -gem 'sidekiq', '2.17.8' +gem 'sidekiq', '~> 3.3' # HTTP requests gem "httparty" @@ -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" @@ -218,7 +221,7 @@ group :development, :test do gem 'spinach-rails' gem "rspec-rails" gem "capybara", '~> 2.2.1' - gem "pry" + gem "pry-rails" gem "awesome_print" gem "database_cleaner" gem "launchy" @@ -251,7 +254,7 @@ end group :test do gem "simplecov", require: false - gem "shoulda-matchers", "~> 2.1.0" + gem "shoulda-matchers", "~> 2.7.0" gem 'email_spec' gem "webmock" gem 'test_after_commit' diff --git a/Gemfile.lock b/Gemfile.lock index d9ba4e3c172..551f16722f2 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) @@ -62,8 +63,8 @@ GEM activemodel (>= 3.2.0) activesupport (>= 3.2.0) json (>= 1.7) - celluloid (0.15.2) - timers (~> 1.1.0) + celluloid (0.16.0) + timers (~> 4.0.0) charlock_holmes (0.6.9.4) cliver (0.3.2) code_analyzer (0.4.3) @@ -244,6 +245,7 @@ GEM hike (1.2.3) hipchat (1.4.0) httparty + hitimes (1.2.2) html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) @@ -258,7 +260,7 @@ GEM multi_xml (>= 0.5.2) httpauth (0.2.1) httpclient (2.5.3.3) - i18n (0.6.11) + i18n (0.7.0) ice_nine (0.10.0) jasmine (2.0.2) jasmine-core (~> 2.0.0) @@ -277,7 +279,7 @@ GEM turbolinks jquery-ui-rails (4.2.1) railties (>= 3.2.16) - json (1.8.1) + json (1.8.2) jwt (0.1.13) multi_json (>= 1.5) kaminari (0.15.1) @@ -364,6 +366,8 @@ GEM coderay (~> 1.0) method_source (~> 0.8) slop (~> 3.4) + pry-rails (0.3.2) + pry (>= 0.9.10) pyu-ruby-sasl (0.0.3.3) quiet_assets (1.0.2) railties (>= 3.1, < 5.0) @@ -489,18 +493,18 @@ 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) - shoulda-matchers (2.1.0) + shoulda-matchers (2.7.0) activesupport (>= 3.0.0) - sidekiq (2.17.8) - celluloid (= 0.15.2) - connection_pool (~> 2.0) + sidekiq (3.3.0) + celluloid (>= 0.16.0) + connection_pool (>= 2.0.0) json - redis (~> 3.1) - redis-namespace (~> 1.3) + redis (>= 3.0.6) + redis-namespace (>= 1.3.1) simple_oauth (0.1.9) simplecov (0.9.0) docile (~> 1.1.0) @@ -555,7 +559,8 @@ GEM thor (0.19.1) thread_safe (0.3.4) tilt (1.4.1) - timers (1.1.0) + timers (4.0.1) + hitimes timfel-krb5-auth (0.8) tinder (1.9.3) eventmachine (~> 1.0) @@ -625,6 +630,7 @@ DEPENDENCIES binding_of_caller bootstrap-sass (~> 3.0) browser + cal-heatmap-rails (~> 0.0.1) capybara (~> 2.2.1) carrierwave coffee-rails @@ -690,7 +696,7 @@ DEPENDENCIES org-ruby (= 0.9.12) pg poltergeist (~> 1.5.1) - pry + pry-rails quiet_assets (~> 1.0.1) rack-attack rack-cors @@ -713,10 +719,10 @@ 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 (= 2.17.8) + shoulda-matchers (~> 2.7.0) + sidekiq (~> 3.3) simplecov sinatra six diff --git a/app/assets/images/authbuttons/github_32.png b/app/assets/images/authbuttons/github_32.png Binary files differindex c56eef05eb9..0445b567bbc 100644 --- a/app/assets/images/authbuttons/github_32.png +++ b/app/assets/images/authbuttons/github_32.png diff --git a/app/assets/images/authbuttons/github_64.png b/app/assets/images/authbuttons/github_64.png Binary files differindex 39de55bc796..dc7c03d1005 100644 --- a/app/assets/images/authbuttons/github_64.png +++ b/app/assets/images/authbuttons/github_64.png diff --git a/app/assets/images/authbuttons/google_32.png b/app/assets/images/authbuttons/google_32.png Binary files differindex 6225cc9c2d7..b03c3ec5207 100644 --- a/app/assets/images/authbuttons/google_32.png +++ b/app/assets/images/authbuttons/google_32.png diff --git a/app/assets/images/authbuttons/google_64.png b/app/assets/images/authbuttons/google_64.png Binary files differindex 4d608f71008..94a0e089c6e 100644 --- a/app/assets/images/authbuttons/google_64.png +++ b/app/assets/images/authbuttons/google_64.png diff --git a/app/assets/images/authbuttons/twitter_32.png b/app/assets/images/authbuttons/twitter_32.png Binary files differindex 696eb02484d..a3d4964f40f 100644 --- a/app/assets/images/authbuttons/twitter_32.png +++ b/app/assets/images/authbuttons/twitter_32.png diff --git a/app/assets/images/authbuttons/twitter_64.png b/app/assets/images/authbuttons/twitter_64.png Binary files differindex 2893274766f..5c9f14cb077 100644 --- a/app/assets/images/authbuttons/twitter_64.png +++ b/app/assets/images/authbuttons/twitter_64.png diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png Binary files differindex 9ecdaf4e2d5..639271c6faf 100644 --- a/app/assets/images/bg-header.png +++ b/app/assets/images/bg-header.png diff --git a/app/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png Binary files differindex d9066ad7d7b..e5fe659ba63 100644 --- a/app/assets/images/bg_fallback.png +++ b/app/assets/images/bg_fallback.png diff --git a/app/assets/images/brand_logo.png b/app/assets/images/brand_logo.png Binary files differindex 09b1689ca45..9c564bb6141 100644 --- a/app/assets/images/brand_logo.png +++ b/app/assets/images/brand_logo.png diff --git a/app/assets/images/chosen-sprite.png b/app/assets/images/chosen-sprite.png Binary files differindex d08e4b7e624..3d936b07d44 100644 --- a/app/assets/images/chosen-sprite.png +++ b/app/assets/images/chosen-sprite.png diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png Binary files differindex 2d631a49fd3..2ef58e52549 100644 --- a/app/assets/images/dark-scheme-preview.png +++ b/app/assets/images/dark-scheme-preview.png diff --git a/app/assets/images/diff_note_add.png b/app/assets/images/diff_note_add.png Binary files differindex 8ec15b701fc..0084422e330 100644 --- a/app/assets/images/diff_note_add.png +++ b/app/assets/images/diff_note_add.png diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png Binary files differindex 32ade0fe9a3..60021d5ac47 100644 --- a/app/assets/images/icon-link.png +++ b/app/assets/images/icon-link.png diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png Binary files differindex 084b89e3a7c..3c1c146541d 100644 --- a/app/assets/images/icon-search.png +++ b/app/assets/images/icon-search.png diff --git a/app/assets/images/icon_sprite.png b/app/assets/images/icon_sprite.png Binary files differindex 9ad65fc443b..2e7a5023398 100644 --- a/app/assets/images/icon_sprite.png +++ b/app/assets/images/icon_sprite.png diff --git a/app/assets/images/images.png b/app/assets/images/images.png Binary files differindex da91f6b1f4c..ad146246caf 100644 --- a/app/assets/images/images.png +++ b/app/assets/images/images.png diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png Binary files differindex 4a96572d570..49cdc16cacd 100644 --- a/app/assets/images/logo-black.png +++ b/app/assets/images/logo-black.png diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png Binary files differindex bc2ef601a53..2299153caba 100644 --- a/app/assets/images/logo-white.png +++ b/app/assets/images/logo-white.png diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png Binary files differindex 6791d1ee33d..fbb339c6a91 100644 --- a/app/assets/images/monokai-scheme-preview.png +++ b/app/assets/images/monokai-scheme-preview.png diff --git a/app/assets/images/move.png b/app/assets/images/move.png Binary files differindex 9d2d55ddf0b..6a0567f8f25 100644 --- a/app/assets/images/move.png +++ b/app/assets/images/move.png diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png Binary files differindex dac3ab1bb89..8287acbce13 100644 --- a/app/assets/images/no_avatar.png +++ b/app/assets/images/no_avatar.png diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png Binary files differindex a97d4515982..bfb31bb2184 100644 --- a/app/assets/images/no_group_avatar.png +++ b/app/assets/images/no_group_avatar.png diff --git a/app/assets/images/slider_handles.png b/app/assets/images/slider_handles.png Binary files differindex a6d477033fa..884378ec96a 100644 --- a/app/assets/images/slider_handles.png +++ b/app/assets/images/slider_handles.png diff --git a/app/assets/images/solarized-dark-scheme-preview.png b/app/assets/images/solarized-dark-scheme-preview.png Binary files differindex 8f904405310..7ed7336896b 100644 --- a/app/assets/images/solarized-dark-scheme-preview.png +++ b/app/assets/images/solarized-dark-scheme-preview.png diff --git a/app/assets/images/solarized-light-scheme-preview.png b/app/assets/images/solarized-light-scheme-preview.png Binary files differindex 7da5d2d2090..c50db75449b 100644 --- a/app/assets/images/solarized-light-scheme-preview.png +++ b/app/assets/images/solarized-light-scheme-preview.png diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png Binary files differindex 6b8bde41bc9..c6b6c8d9521 100644 --- a/app/assets/images/switch_icon.png +++ b/app/assets/images/switch_icon.png diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif Binary files differindex 5f6ed04a43c..1a1c9c15ec7 100644 --- a/app/assets/images/trans_bg.gif +++ b/app/assets/images/trans_bg.gif diff --git a/app/assets/images/white-scheme-preview.png b/app/assets/images/white-scheme-preview.png Binary files differindex d32b7485e1e..fc4c40b9227 100644 --- a/app/assets/images/white-scheme-preview.png +++ b/app/assets/images/white-scheme-preview.png diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 9b36a172ba6..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) -> @@ -74,24 +75,18 @@ window.disableButtonIfEmptyField = (field_selector, button_selector) -> # Disable button if any input field with given selector is empty window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) -> closest_submit = form.find(button_selector) - empty = false - form.find('input').filter(form_selector).each -> - empty = true if rstrip($(this).val()) is "" - - if empty - closest_submit.disable() - else - closest_submit.enable() - - form.keyup -> - empty = false + updateButtons = -> + filled = true form.find('input').filter(form_selector).each -> - empty = true if rstrip($(this).val()) is "" + filled = rstrip($(this).val()) != "" || !$(this).attr('required') - if empty - closest_submit.disable() - else + if filled closest_submit.enable() + else + closest_submit.disable() + + updateButtons() + form.keyup(updateButtons) window.sanitize = (str) -> return str.replace(/<(?:.|\n)*?>/gm, '') diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob/blob.js.coffee index a5f15f80c5c..a5f15f80c5c 100644 --- a/app/assets/javascripts/blob.js.coffee +++ b/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..70940e13858 --- /dev/null +++ b/app/assets/javascripts/calendar.js.coffee @@ -0,0 +1,33 @@ +class @calendar + options = + month: "short" + day: "numeric" + year: "numeric" + + constructor: (timestamps, starting_year, starting_month) -> + cal = new CalHeatMap() + cal.init + itemName: ["commit"] + data: timestamps + 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/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index ef86c2781c9..1643ca941ff 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -96,6 +96,7 @@ class Dispatcher new Profile() when 'projects' new Project() + new ProjectAvatar() switch path[1] when 'edit' shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/project_avatar.js.coffee b/app/assets/javascripts/project_avatar.js.coffee new file mode 100644 index 00000000000..8bec6e2ccca --- /dev/null +++ b/app/assets/javascripts/project_avatar.js.coffee @@ -0,0 +1,9 @@ +class @ProjectAvatar + constructor: -> + $('.js-choose-project-avatar-button').bind 'click', -> + form = $(this).closest('form') + form.find('.js-project-avatar-input').click() + $('.js-project-avatar-input').bind 'change', -> + form = $(this).closest('form') + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find('.js-avatar-filename').text(filename) 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/avatar.scss b/app/assets/stylesheets/generic/avatar.scss index 4f038b977e2..b88cdd83937 100644 --- a/app/assets/stylesheets/generic/avatar.scss +++ b/app/assets/stylesheets/generic/avatar.scss @@ -2,15 +2,21 @@ float: left; margin-right: 12px; width: 40px; - padding: 1px; - @include border-radius(4px); + height: 40px; + padding: 0; + @include border-radius($avatar_radius); &.avatar-inline { float: none; - margin-left: 3px; + margin-left: 4px; + margin-bottom: 2px; - &.s16 { margin-right: 2px; } - &.s24 { margin-right: 2px; } + &.s16 { margin-right: 4px; } + &.s24 { margin-right: 4px; } + } + + &.avatar-tile { + @include border-radius(0px); } &.s16 { width: 16px; height: 16px; margin-right: 6px; } @@ -21,3 +27,16 @@ &.s90 { width: 90px; height: 90px; margin-right: 15px; } &.s160 { width: 160px; height: 160px; margin-right: 20px; } } + +.identicon { + text-align: center; + vertical-align: top; + + &.s16 { font-size: 12px; line-height: 1.33; } + &.s24 { font-size: 14px; line-height: 1.8; } + &.s26 { font-size: 20px; line-height: 1.33; } + &.s32 { font-size: 24px; line-height: 1.33; } + &.s60 { font-size: 45px; line-height: 1.33; } + &.s90 { font-size: 68px; line-height: 1.33; } + &.s160 { font-size: 120px; line-height: 1.33; } +} 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/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss index 82ee41b71bd..f92a79f7a5f 100644 --- a/app/assets/stylesheets/generic/timeline.scss +++ b/app/assets/stylesheets/generic/timeline.scss @@ -42,7 +42,7 @@ background: #fff; color: #737881; float: left; - @include border-radius(40px); + @include border-radius($avatar_radius); @include box-shadow(0 0 0 3px #EEE); overflow: hidden; @@ -58,6 +58,10 @@ padding: 10px 15px; margin-left: 60px; + img { + max-width: 100%; + } + &:after { content: ''; display: block; diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index 385a627b4be..58243bc5ba2 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -128,3 +128,7 @@ a:focus { textarea.js-gfm-input { font-family: $monospace_font; } + +.strikethrough { + text-decoration: line-through; +}
\ No newline at end of file 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 aded9cb549d..acbf5be94a3 100644 --- a/app/assets/stylesheets/main/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -2,7 +2,7 @@ * General Colors */ $style_color: #474D57; -$hover: #FFECDB; +$hover: #FFF3EB; $box_bg: #F9F9F9; /* @@ -56,4 +56,8 @@ $list-font-size: 15px; /** * Sidebar navigation width */ -$sidebar_width: 240px; +$sidebar_width: 230px; + +$avatar_radius: 50%; +$code_font_size: 13px; +$code_line_height: 1.5; diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index 2fd2dcba475..2e274d06c12 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -101,7 +101,6 @@ .commit-title { margin: 0; - font-size: 20px; } .commit-description { diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index e540f7ff940..90010781af0 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -75,6 +75,9 @@ } } } +.project-avatar { + float: left; +} .project-description { overflow: hidden; @@ -92,8 +95,24 @@ } } +.dash-project-avatar { + float: left; +} .dash-project-access-icon { float: left; - margin-right: 3px; + margin-right: 5px; width: 16px; } + +.dash-new-project { + background: $bg_success; + border: 1px solid $border_success; + + a { + 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 a5098b6da5b..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(4px); + 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/note_form.scss b/app/assets/stylesheets/sections/note_form.scss index 26511d799f3..61a877a5e43 100644 --- a/app/assets/stylesheets/sections/note_form.scss +++ b/app/assets/stylesheets/sections/note_form.scss @@ -52,6 +52,10 @@ } } + img { + max-width: 100%; + } + .note_text { width: 100%; } diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index 086875582f3..0ab62b7ae49 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -102,12 +102,3 @@ } } } - -.profile-groups-avatars { - margin: 0 5px 10px 0; - - img { - width: 50px; - height: 50px; - } -} 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 6da4f91c3f4..36e13706768 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -49,7 +49,7 @@ class ApplicationController < ActionController::Base end def authenticate_user!(*args) - # If user is not signe-in and tries to access root_path - redirect him to landing page + # If user is not signed-in and tries to access root_path - redirect him to landing page if current_application_settings.home_page_url.present? if current_user.nil? && controller_name == 'dashboard' && action_name == 'show' redirect_to current_application_settings.home_page_url and return @@ -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/github_imports_controller.rb b/app/controllers/github_imports_controller.rb index ac5cfd8d45d..3c5448bc709 100644 --- a/app/controllers/github_imports_controller.rb +++ b/app/controllers/github_imports_controller.rb @@ -63,11 +63,11 @@ class GithubImportsController < ApplicationController def github_auth if current_user.github_access_token.blank? - go_to_gihub_for_permissions + go_to_github_for_permissions end end - def go_to_gihub_for_permissions + def go_to_github_for_permissions redirect_to client.auth_code.authorize_url({ redirect_uri: callback_github_import_url, scope: "repo, user, user:email" @@ -75,6 +75,6 @@ class GithubImportsController < ApplicationController end def github_unauthorized - go_to_gihub_for_permissions + go_to_github_for_permissions end end diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 988ede3007b..dcbbe5baa4b 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -5,12 +5,12 @@ class PasswordsController < Devise::PasswordsController resource_found = resource_class.find_by_email(email) if resource_found && resource_found.ldap_user? flash[:alert] = "Cannot reset password for LDAP user." - respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) and return + respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) and return end self.resource = resource_class.send_reset_password_instructions(resource_params) if successfully_sent?(resource) - respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) + respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) else respond_with(resource) end diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb new file mode 100644 index 00000000000..a482b90880d --- /dev/null +++ b/app/controllers/projects/avatars_controller.rb @@ -0,0 +1,29 @@ +class Projects::AvatarsController < Projects::ApplicationController + layout 'project' + + before_filter :project + + def show + @blob = @project.repository.blob_at_branch('master', @project.avatar_in_git) + if @blob + headers['X-Content-Type-Options'] = 'nosniff' + send_data( + @blob.data, + type: @blob.mime_type, + disposition: 'inline', + filename: @blob.name + ) + else + not_found! + end + end + + def destroy + @project.remove_avatar! + + @project.save + @project.reset_events_cache + + redirect_to edit_project_path(@project) + end +end 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 + # Raised when given an invalid file path + class InvalidPathError < StandardError; end + before_filter :authorize_download_code! - before_filter :require_non_empty_project + 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 - before_filter :blob + 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/commit_controller.rb b/app/controllers/projects/commit_controller.rb index dac858d8e16..470efbd2114 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -12,6 +12,7 @@ class Projects::CommitController < Projects::ApplicationController @line_notes = @project.notes.for_commit_id(commit.id).inline @branches = @project.repository.branch_names_contains(commit.id) + @tags = @project.repository.tag_names_contains(commit.id) @diffs = @commit.diffs @note = @project.build_commit_note(commit) @notes_count = @project.notes.for_commit_id(commit.id).count 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/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index 02160d973b3..f45df38b87c 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -24,7 +24,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ) respond_to do |format| - format.json { render :json => protected_branch, status: :ok } + format.json { render json: protected_branch, status: :ok } end else respond_to do |format| 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 5d9336bdc49..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 @@ -41,9 +41,9 @@ class Projects::RefsController < Projects::ApplicationController @path = params[:path] contents = [] - contents += tree.trees - contents += tree.blobs - contents += tree.submodules + contents.push(*tree.trees) + contents.push(*tree.blobs) + contents.push(*tree.submodules) @logs = contents[@offset, @limit].to_a.map do |content| file = @path ? File.join(@path, content.name) : content.name diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index b2ce99aeb45..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 @@ -25,9 +26,13 @@ class Projects::ServicesController < Projects::ApplicationController def test data = Gitlab::PushDataBuilder.build_sample(project, current_user) - @service.execute(data) + if @service.execute(data) + message = { notice: 'We sent a request to the provided URL' } + else + message = { alert: 'We tried to send a request to the provided URL but error occured' } + end - redirect_to :back + redirect_to :back, message end private @@ -41,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/projects_controller.rb b/app/controllers/projects_controller.rb index e541b6fd872..ebe48265c63 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -14,7 +14,7 @@ class ProjectsController < ApplicationController end def edit - render 'edit', layout: "project_settings" + render 'edit', layout: 'project_settings' end def create @@ -36,7 +36,7 @@ class ProjectsController < ApplicationController format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' } format.js else - format.html { render "edit", layout: "project_settings" } + format.html { render 'edit', layout: 'project_settings' } format.js end end @@ -66,17 +66,17 @@ class ProjectsController < ApplicationController format.html do if @project.repository_exists? if @project.empty_repo? - render "projects/empty", layout: user_layout + render 'projects/empty', layout: user_layout else @last_push = current_user.recent_push(@project.id) if current_user render :show, layout: user_layout end else - render "projects/no_repo", layout: user_layout + render 'projects/no_repo', layout: user_layout end end - format.json { pager_json("events/_events", @events.count) } + format.json { pager_json('events/_events', @events.count) } end end @@ -87,9 +87,9 @@ class ProjectsController < ApplicationController respond_to do |format| format.html do - flash[:alert] = "Project deleted." + flash[:alert] = 'Project deleted.' - if request.referer.include?("/admin") + if request.referer.include?('/admin') redirect_to admin_projects_path else redirect_to projects_dashboard_path @@ -101,16 +101,18 @@ class ProjectsController < ApplicationController def autocomplete_sources note_type = params['type'] note_id = params['type_id'] + autocomplete = ::Projects::AutocompleteService.new(@project) participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id) + @suggestions = { - emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } }, - issues: @project.issues.select([:iid, :title, :description]), - mergerequests: @project.merge_requests.select([:iid, :title, :description]), + emojis: autocomplete_emojis, + issues: autocomplete.issues, + mergerequests: autocomplete.merge_requests, members: participants } respond_to do |format| - format.json { render :json => @suggestions } + format.json { render json: @suggestions } end end @@ -139,7 +141,7 @@ class ProjectsController < ApplicationController if link_to_image format.json { render json: { link: link_to_image } } else - format.json { render json: "Invalid file.", status: :unprocessable_entity } + format.json { render json: 'Invalid file.', status: :unprocessable_entity } end end end @@ -170,14 +172,25 @@ class ProjectsController < ApplicationController end def user_layout - current_user ? "projects" : "public_projects" + current_user ? 'projects' : 'public_projects' end def project_params params.require(:project).permit( :name, :path, :description, :issues_tracker, :tag_list, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, - :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id + :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar ) end + + def autocomplete_emojis + Rails.cache.fetch("autocomplete-emoji-#{Emoji::VERSION}") do + Emoji.names.map do |e| + { + name: e, + path: view_context.image_url("emoji/#{e}.png") + } + end + end + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 67af1801bda..57d8ef09faf 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,16 @@ class UsersController < ApplicationController end end + def calendar + visible_projects = ProjectsFinder.new.execute(current_user) + calendar = Gitlab::CommitsCalendar.new(visible_projects, @user) + @timestamps = calendar.timestamps + @starting_year = calendar.starting_year + @starting_month = calendar.starting_month + + render 'calendar', layout: false + end + def determine_layout if current_user 'navless' @@ -37,4 +43,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 f65e04af205..d00f1aac2d6 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -50,6 +50,38 @@ module ApplicationHelper args.any? { |v| v.to_s.downcase == action_name } end + def project_icon(project_id, options = {}) + project = Project.find_with_namespace(project_id) + if project.avatar.present? + image_tag project.avatar.url, options + elsif project.avatar_in_git + image_tag project_avatar_path(project), options + else # generated icon + project_identicon(project, options) + end + end + + def project_identicon(project, options = {}) + allowed_colors = { + red: 'FFEBEE', + purple: 'F3E5F5', + indigo: 'E8EAF6', + blue: 'E3F2FD', + teal: 'E0F2F1', + orange: 'FBE9E7', + gray: 'EEEEEE' + } + + options[:class] ||= '' + options[:class] << ' identicon' + bg_key = project.id % 7 + + content_tag(:div, class: options[:class], + style: "background-color: ##{ allowed_colors.values[bg_key] }; color: #555") do + project.name[0, 1].upcase + end + end + def group_icon(group_path) group = Group.find_by(path: group_path) if group && group.avatar.present? @@ -82,24 +114,24 @@ module ApplicationHelper if project.repo_exists? time_ago_with_tooltip(project.repository.commit.committed_date) else - "Never" + 'Never' end rescue - "Never" + 'Never' end def grouped_options_refs repository = @project.repository options = [ - ["Branches", repository.branch_names], - ["Tags", VersionSorter.rsort(repository.tag_names)] + ['Branches', repository.branch_names], + ['Tags', VersionSorter.rsort(repository.tag_names)] ] # If reference is commit id - we should add it to branch/tag selectbox if(@ref && !options.flatten.include?(@ref) && @ref =~ /^[0-9a-zA-Z]{6,52}$/) - options << ["Commit", [@ref]] + options << ['Commit', [@ref]] end grouped_options_for_select(options, @ref || @project.default_branch) @@ -161,7 +193,7 @@ module ApplicationHelper path = controller.controller_path.split('/') namespace = path.first if path.second - [namespace, controller.controller_name, controller.action_name].compact.join(":") + [namespace, controller.controller_name, controller.action_name].compact.join(':') end # shortcut for gitlab config @@ -176,13 +208,13 @@ module ApplicationHelper def search_placeholder if @project && @project.persisted? - "Search in this project" + 'Search in this project' elsif @snippet || @snippets || @show_snippets 'Search snippets' elsif @group && @group.persisted? - "Search in this group" + 'Search in this group' else - "Search" + 'Search' end end @@ -193,7 +225,7 @@ module ApplicationHelper def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago') capture_haml do haml_tag :time, date.to_s, - class: html_class, datetime: date.getutc.iso8601, title: date.stamp("Aug 21, 2011 9:23pm"), + class: html_class, datetime: date.getutc.iso8601, title: date.stamp('Aug 21, 2011 9:23pm'), data: { toggle: 'tooltip', placement: placement } haml_tag :script, "$('." + html_class + "').timeago().tooltip()" @@ -215,15 +247,6 @@ module ApplicationHelper Gitlab::MarkdownHelper.gitlab_markdown?(filename) end - def spinner(text = nil, visible = false) - css_class = "loading" - css_class << " hide" unless visible - - content_tag :div, class: css_class do - content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text - end - end - def link_to(name = nil, options = nil, html_options = nil, &block) begin uri = URI(options) @@ -234,17 +257,17 @@ module ApplicationHelper absolute_uri = nil end - # Add "nofollow" only to external links + # Add 'nofollow' only to external links if host && host != Gitlab.config.gitlab.host && absolute_uri if html_options if html_options[:rel] - html_options[:rel] << " nofollow" + html_options[:rel] << ' nofollow' else - html_options.merge!(rel: "nofollow") + html_options.merge!(rel: 'nofollow') end else html_options = Hash.new - html_options[:rel] = "nofollow" + html_options[:rel] = 'nofollow' end 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/commits_helper.rb b/app/helpers/commits_helper.rb index 36adeadd8a5..1a322ac048f 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -44,7 +44,7 @@ module CommitsHelper parts = @path.split('/') parts.each_with_index do |part, i| - crumbs += content_tag(:li) do + crumbs << content_tag(:li) do # The text is just the individual part, but the link needs all the parts before it link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/'))) end @@ -62,7 +62,25 @@ module CommitsHelper # Returns the sorted alphabetically links to branches, separated by a comma def commit_branches_links(project, branches) - branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe + branches.sort.map do |branch| + link_to(project_tree_path(project, branch)) do + content_tag :span, class: 'label label-gray' do + icon('code-fork') + ' ' + branch + end + end + end.join(" ").html_safe + end + + # Returns the sorted links to tags, separated by a comma + def commit_tags_links(project, tags) + sorted = VersionSorter.rsort(tags) + sorted.map do |tag| + link_to(project_commits_path(project, project.repository.find_tag(tag).name)) do + content_tag :span, class: 'label label-gray' do + icon('tag') + ' ' + tag + end + end + end.join(" ").html_safe end def link_to_browse_code(project, commit) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index a15af0be01a..8c921cba543 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -135,4 +135,19 @@ module DiffHelper 'Side-by-side' end end + + def submodule_link(blob, ref) + tree, commit = submodule_links(blob, ref) + commit_id = if commit.nil? + blob.id[0..10] + else + link_to "#{blob.id[0..10]}", commit + end + + [ + content_tag(:span, link_to(truncate(blob.name, length: 40), tree)), + '@', + content_tag(:span, commit_id, class: 'monospace'), + ].join(' ').html_safe + end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 903a5009616..d05f6df5f9f 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -27,18 +27,17 @@ module EventsHelper content_tag :li, class: "filter_icon #{active}" do link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do - content_tag(:i, nil, class: icon_for_event[key]) + - content_tag(:span, ' ' + tooltip) + icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) end end end def icon_for_event { - EventFilter.push => 'fa fa-upload', - EventFilter.merged => 'fa fa-check-square-o', - EventFilter.comments => 'fa fa-comments', - EventFilter.team => 'fa fa-user', + EventFilter.push => 'upload', + EventFilter.merged => 'check-square-o', + EventFilter.comments => 'comments', + EventFilter.team => 'user', } end diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb index 7cb1b6f8d1a..e1dda20de85 100644 --- a/app/helpers/graph_helper.rb +++ b/app/helpers/graph_helper.rb @@ -1,10 +1,10 @@ module GraphHelper def get_refs(repo, commit) refs = "" - refs += commit.ref_names(repo).join(" ") + refs << commit.ref_names(repo).join(' ') # append note count - refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 + refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 refs end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index aaa8f8d0077..18260f0ed4d 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -1,21 +1,39 @@ module IconsHelper + # Creates an icon tag given icon name(s) and possible icon modifiers. + # + # Right now this method simply delegates directly to `fa_icon` from the + # font-awesome-rails gem, but should we ever use a different icon pack in the + # future we won't have to change hundreds of method calls. + def icon(names, options = {}) + fa_icon(names, options) + end + + def spinner(text = nil, visible = false) + css_class = 'loading' + css_class << ' hide' unless visible + + content_tag :div, class: css_class do + icon('spinner spin') + text + end + end + def boolean_to_icon(value) if value.to_s == "true" - content_tag :i, nil, class: 'fa fa-circle cgreen' + icon('circle', class: 'cgreen') else - content_tag :i, nil, class: 'fa fa-power-off clgray' + icon('power-off', class: 'clgray') end end def public_icon - content_tag :i, nil, class: 'fa fa-globe' + icon('globe') end def internal_icon - content_tag :i, nil, class: 'fa fa-shield' + icon('shield') end def private_icon - content_tag :i, nil, class: 'fa fa-lock' + icon('lock') end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index bcf108c5c48..e1c1078344e 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 @@ -69,7 +49,7 @@ module IssuesHelper ts << capture_haml do haml_tag :span do haml_concat '·' - haml_concat '<i class="fa fa-edit" title="edited"></i> ' + haml_concat icon('edit', title: 'edited') haml_concat time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago') end 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/notes_helper.rb b/app/helpers/notes_helper.rb index 8f493f5d331..d41d5617396 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -22,7 +22,7 @@ module NotesHelper ts << capture_haml do haml_tag :span do haml_concat '·' - haml_concat '<i class="fa fa-edit" title="edited"></i> ' + haml_concat icon('edit', title: 'edited') haml_concat time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago') end end @@ -57,7 +57,7 @@ module NotesHelper button_tag(class: 'btn add-diff-note js-add-diff-note-button', data: data, title: 'Add a comment to this line') do - content_tag :i, nil, class: 'fa fa-comment-o' + icon('comment-o') end end @@ -74,7 +74,7 @@ module NotesHelper button_tag class: 'btn reply-btn js-discussion-reply-button', data: data, title: 'Add a reply' do - link_text = content_tag(:i, nil, class: 'fa fa-comment') + link_text = icon('comment') link_text << ' Reply' end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index bad380e98a8..f771fe761ef 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -1,13 +1,13 @@ module NotificationsHelper def notification_icon(notification) if notification.disabled? - content_tag :i, nil, class: 'fa fa-volume-off ns-mute' + icon('volume-off', class: 'ns-mute') elsif notification.participating? - content_tag :i, nil, class: 'fa fa-volume-down ns-part' + icon('volume-down', class: 'ns-part') elsif notification.watch? - content_tag :i, nil, class: 'fa fa-volume-up ns-watch' + icon('volume-up', class: 'ns-watch') else - content_tag :i, nil, class: 'fa fa-circle-o ns-default' + icon('circle-o', class: 'ns-default') end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 9a31d485188..0b01be79623 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -72,21 +72,9 @@ 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 + cls << ' disabled' unless signed_in toggle_html = content_tag('span', class: 'toggle') do toggle_text = if starred @@ -95,7 +83,7 @@ module ProjectsHelper ' Star' end - content_tag('i', ' ', class: 'fa fa-star') + toggle_text + icon('star') + toggle_text end count_html = content_tag('span', class: 'count') do @@ -119,7 +107,7 @@ module ProjectsHelper end def link_to_toggle_fork - out = content_tag(:i, '', class: 'fa fa-code-fork') + out = icon('code-fork') out << ' Fork' out << content_tag(:span, class: 'count') do @project.forks_count.to_s @@ -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/submodule_helper.rb b/app/helpers/submodule_helper.rb index 09e5c08e621..841e7fd17f6 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -2,8 +2,8 @@ module SubmoduleHelper include Gitlab::ShellAdapter # links to files listing for submodule if submodule is a project on this server - def submodule_links(submodule_item) - url = @repository.submodule_url_for(@ref, submodule_item.path) + def submodule_links(submodule_item, ref = nil) + url = @repository.submodule_url_for(ref, submodule_item.path) return url, nil unless url =~ /([^\/:]+\/[^\/]+\.git)\Z/ diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index ef89bb32c6d..fb85544df2d 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -6,7 +6,7 @@ module TagsHelper def tag_list(project) html = '' project.tag_list.each do |tag| - html += link_to tag, tag_path(tag) + html << link_to(tag, tag_path(tag)) end html.html_safe diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 133b0dfae9b..b6fb7a8aa5a 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -10,13 +10,16 @@ module TreeHelper tree = "" # Render folders if we have any - tree += render partial: 'projects/tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present? + tree << render(partial: 'projects/tree/tree_item', collection: folders, + locals: { type: 'folder' }) if folders.present? # Render files if we have any - tree += render partial: 'projects/tree/blob_item', collection: files, locals: {type: 'file'} if files.present? + tree << render(partial: 'projects/tree/blob_item', collection: files, + locals: { type: 'file' }) if files.present? # Render submodules if we have any - tree += render partial: 'projects/tree/submodule_item', collection: submodules if submodules.present? + tree << render(partial: 'projects/tree/submodule_item', + collection: submodules) if submodules.present? tree.html_safe end @@ -35,13 +38,8 @@ module TreeHelper # # type - String type of the tree item; either 'folder' or 'file' def tree_icon(type) - icon_class = if type == 'folder' - 'fa fa-folder' - else - 'fa fa-file-o' - end - - content_tag :i, nil, class: icon_class + icon_class = type == 'folder' ? 'folder' : 'file-o' + icon(icon_class) end def tree_hex_class(content) @@ -61,32 +59,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 = "" @@ -109,7 +81,7 @@ module TreeHelper tree_join(@ref, file) end - # returns the relative path of the first subdir that doesn't have only one directory descendand + # returns the relative path of the first subdir that doesn't have only one directory descendant def flatten_tree(tree) subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path) if subtree.count == 1 && subtree.first.dir? @@ -118,16 +90,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/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 9ecdac87d72..7f6c855c301 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -56,7 +56,7 @@ module Emails end end - # Over rides default behavour to show source/target + # Over rides default behaviour to show source/target # Formats arguments into a String suitable for use as an email subject # # extra - Extra Strings to be inserted into the subject diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 6d671e6e0bd..5ae07d771fa 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -26,8 +26,8 @@ class Notify < ActionMailer::Base delay_for(2.seconds) end - def test_email(recepient_email, subject, body) - mail(to: recepient_email, + def test_email(recipient_email, subject, body) + mail(to: recipient_email, subject: subject, body: body.html_safe, content_type: 'text/html' diff --git a/app/models/ability.rb b/app/models/ability.rb index 97a72bf3635..890417e780d 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -73,28 +73,28 @@ class Ability # Rules based on role in project if team.master?(user) - rules += project_master_rules + rules.push(*project_master_rules) elsif team.developer?(user) - rules += project_dev_rules + rules.push(*project_dev_rules) elsif team.reporter?(user) - rules += project_report_rules + rules.push(*project_report_rules) elsif team.guest?(user) - rules += project_guest_rules + rules.push(*project_guest_rules) end if project.public? || project.internal? - rules += public_project_rules + rules.push(*public_project_rules) end if project.owner == user || user.admin? - rules += project_admin_rules + rules.push(*project_admin_rules) end if project.group && project.group.has_owner?(user) - rules += project_admin_rules + rules.push(*project_admin_rules) end if project.archived? @@ -193,17 +193,17 @@ class Ability # Only group masters and group owners can create new projects in group if group.has_master?(user) || group.has_owner?(user) || user.admin? - rules += [ + rules.push(*[ :create_projects, - ] + ]) end # Only group owner and administrators can manage group if group.has_owner?(user) || user.admin? - rules += [ + rules.push(*[ :manage_group, :manage_namespace - ] + ]) end rules.flatten @@ -214,10 +214,10 @@ class Ability # Only namespace owner and administrators can manage it if namespace.owner == user || user.admin? - rules += [ + rules.push(*[ :create_projects, :manage_namespace - ] + ]) end rules.flatten diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index aed4068f309..45ae79a75cc 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: application_settings +# +# id :integer not null, primary key +# default_projects_limit :integer +# signup_enabled :boolean +# signin_enabled :boolean +# gravatar_enabled :boolean +# sign_in_text :text +# created_at :datetime +# updated_at :datetime +# home_page_url :string(255) +# + class ApplicationSetting < ActiveRecord::Base validates :home_page_url, allow_blank: true, format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }, diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index f49708fd6eb..fb038a3cc3f 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -88,7 +88,7 @@ module Issuable # Return the number of -1 comments (downvotes) def downvotes - notes.select(&:downvote?).size + filter_superceded_votes(notes.select(&:downvote?), notes).size end def downvotes_in_percent @@ -101,7 +101,7 @@ module Issuable # Return the number of +1 comments (upvotes) def upvotes - notes.select(&:upvote?).size + filter_superceded_votes(notes.select(&:upvote?), notes).size end def upvotes_in_percent @@ -124,10 +124,12 @@ module Issuable users << assignee if is_assigned? mentions = [] mentions << self.mentioned_users + notes.each do |note| users << note.author mentions << note.mentioned_users end + users.concat(mentions.reduce([], :|)).uniq end @@ -149,9 +151,23 @@ module Issuable def add_labels_by_names(label_names) label_names.each do |label_name| - label = project.labels.create_with( - color: Label::DEFAULT_COLOR).find_or_create_by(title: label_name.strip) + label = project.labels.create_with(color: Label::DEFAULT_COLOR). + find_or_create_by(title: label_name.strip) self.labels << label end end + + private + + def filter_superceded_votes(votes, notes) + filteredvotes = [] + votes + + votes.each do |vote| + if vote.superceded?(notes) + filteredvotes.delete(vote) + end + end + + filteredvotes + end end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 6c1aa99668a..66f83b932d4 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -50,7 +50,7 @@ module Mentionable matches.each do |match| identifier = match.delete "@" if identifier == "all" - users += project.team.members.flatten + users.push(*project.team.members.flatten) else id = User.find_by(username: identifier).try(:id) users << User.find(id) unless id.blank? 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/identity.rb b/app/models/identity.rb index 5fb1850c30b..c7cdb63e3d2 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -1,5 +1,15 @@ +# == Schema Information +# +# Table name: identities +# +# id :integer not null, primary key +# extern_uid :string(255) +# provider :string(255) +# user_id :integer +# + class Identity < ActiveRecord::Base belongs_to :user validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} -end
\ No newline at end of file +end diff --git a/app/models/key.rb b/app/models/key.rb index 65a426d1f8b..d2d1af68822 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -19,7 +19,7 @@ class Key < ActiveRecord::Base belongs_to :user - before_validation :strip_white_space, :generate_fingerpint + before_validation :strip_white_space, :generate_fingerprint validates :title, presence: true, length: { within: 0..255 } validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true @@ -76,7 +76,7 @@ class Key < ActiveRecord::Base private - def generate_fingerpint + def generate_fingerprint self.fingerprint = nil return unless key.present? 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/merge_request.rb b/app/models/merge_request.rb index de0ee0e2c5a..715257f905f 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -18,6 +18,7 @@ # iid :integer # description :text # position :integer default(0) +# locked_at :datetime # require Rails.root.join("app/models/commit") @@ -250,7 +251,8 @@ class MergeRequest < ActiveRecord::Base def closes_issues if target_branch == project.default_branch issues = commits.flat_map { |c| c.closes_issues(project) } - issues += Gitlab::ClosingIssueExtractor.closed_by_message_in_project(description, project) + issues.push(*Gitlab::ClosingIssueExtractor. + closed_by_message_in_project(description, project)) issues.uniq.sort_by(&:id) else [] @@ -330,7 +332,7 @@ class MergeRequest < ActiveRecord::Base end # Return array of possible target branches - # dependes on target project of MR + # depends on target project of MR def target_branches if target_project.nil? [] @@ -340,7 +342,7 @@ class MergeRequest < ActiveRecord::Base end # Return array of possible source branches - # dependes on source project of MR + # depends on source project of MR def source_branches if source_project.nil? [] diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index 43979b5e807..f4e90125373 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -84,7 +84,7 @@ module Network skip += self.class.max_count end else - # Cant't find the target commit in the repo. + # Can't find the target commit in the repo. offset = 0 end end @@ -226,7 +226,7 @@ module Network reserved = [] for day in time_range - reserved += @reserved[day] + reserved.push(*@reserved[day]) end reserved.uniq! diff --git a/app/models/note.rb b/app/models/note.rb index e99bc2668d6..0b988cc3e0f 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -459,6 +459,26 @@ class Note < ActiveRecord::Base ) end + def superceded?(notes) + return false unless vote? + + notes.each do |note| + next if note == self + + if note.vote? && + self[:author_id] == note[:author_id] && + self[:created_at] <= note[:created_at] + return true + end + end + + false + end + + def vote? + upvote? || downvote? + end + def votable? for_issue? || (for_merge_request? && !for_diff_line?) end @@ -480,7 +500,7 @@ class Note < ActiveRecord::Base end # FIXME: Hack for polymorphic associations with STI - # For more information wisit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations + # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations def noteable_type=(sType) super(sType.to_s.classify.constantize.base_class.to_s) end diff --git a/app/models/project.rb b/app/models/project.rb index a22f852de6b..f3dddc28adb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -24,8 +24,14 @@ # import_status :string(255) # repository_size :float default(0.0) # star_count :integer default(0), not null +# import_type :string(255) +# import_source :string(255) +# avatar :string(255) # +require 'carrierwave/orm/activerecord' +require 'file_size_validator' + class Project < ActiveRecord::Base include Gitlab::ShellAdapter include Gitlab::VisibilityLevel @@ -47,8 +53,8 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch # Relations - belongs_to :creator, foreign_key: "creator_id", class_name: "User" - belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id" + belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' + belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' @@ -68,20 +74,26 @@ class Project < ActiveRecord::Base has_one :bamboo_service, dependent: :destroy has_one :teamcity_service, dependent: :destroy has_one :pushover_service, dependent: :destroy + 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" + has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' # Merge requests from source project should be kept when source project was removed - has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest + has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy has_many :labels, dependent: :destroy has_many :services, dependent: :destroy has_many :events, dependent: :destroy has_many :milestones, dependent: :destroy has_many :notes, dependent: :destroy - has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" - has_many :hooks, dependent: :destroy, class_name: "ProjectHook" + has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet' + has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :protected_branches, dependent: :destroy has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember' has_many :users, through: :project_members @@ -113,28 +125,31 @@ class Project < ActiveRecord::Base validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id validates :import_url, - format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" }, + format: { with: URI::regexp(%w(git http https)), message: 'should be a valid url' }, if: :import? validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create + validate :avatar_type, + if: ->(project) { project.avatar && project.avatar_changed? } + validates :avatar, file_size: { maximum: 200.kilobytes.to_i } + + mount_uploader :avatar, AttachmentUploader # Scopes - scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } - scope :without_team, ->(team) { team.projects.present? ? where("projects.id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped } - scope :not_in_group, ->(group) { where("projects.id NOT IN (:ids)", ids: group.project_ids ) } - scope :in_team, ->(team) { where("projects.id IN (:ids)", ids: team.projects.map(&:id)) } + scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) } + scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped } + scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) } + scope :in_team, ->(team) { where('projects.id IN (:ids)', ids: team.projects.map(&:id)) } scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } scope :in_group_namespace, -> { joins(:group) } - scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } - scope :sorted_by_stars, -> { reorder("projects.star_count DESC") } + scope :sorted_by_activity, -> { reorder('projects.last_activity_at DESC') } + scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } - scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } + scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :public_only, -> { where(visibility_level: Project::PUBLIC) } 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 @@ -170,7 +185,7 @@ class Project < ActiveRecord::Base def publicish(user) visibility_levels = [Project::PUBLIC] - visibility_levels += [Project::INTERNAL] if user + visibility_levels << Project::INTERNAL if user where(visibility_level: visibility_levels) end @@ -179,26 +194,26 @@ class Project < ActiveRecord::Base end def active - joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC") + joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') end def search(query) - joins(:namespace).where("projects.archived = ?", false). - where("LOWER(projects.name) LIKE :query OR + joins(:namespace).where('projects.archived = ?', false). + where('LOWER(projects.name) LIKE :query OR LOWER(projects.path) LIKE :query OR LOWER(namespaces.name) LIKE :query OR - LOWER(projects.description) LIKE :query", + LOWER(projects.description) LIKE :query', query: "%#{query.try(:downcase)}%") end def search_by_title(query) - where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%") + where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%") end def find_with_namespace(id) - return nil unless id.include?("/") + return nil unless id.include?('/') - id = id.split("/") + id = id.split('/') namespace = Namespace.find_by(path: id.first) return nil unless namespace @@ -216,7 +231,7 @@ class Project < ActiveRecord::Base when 'recently_updated' then reorder('projects.updated_at DESC') when 'last_updated' then reorder('projects.updated_at ASC') when 'largest_repository' then reorder('projects.repository_size DESC') - else reorder("namespaces.path, projects.name ASC") + else reorder('namespaces.path, projects.name ASC') end end end @@ -266,19 +281,19 @@ class Project < ActiveRecord::Base end def to_param - namespace.path + "/" + path + namespace.path + '/' + path end def web_url - [gitlab_config.url, path_with_namespace].join("/") + [gitlab_config.url, path_with_namespace].join('/') end def web_url_without_protocol - web_url.split("://")[1] + web_url.split('://')[1] end def build_commit_note(commit) - notes.new(commit_id: commit.id, noteable_type: "Commit") + notes.new(commit_id: commit.id, noteable_type: 'Commit') end def last_activity @@ -294,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 @@ -321,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? @@ -336,6 +375,24 @@ class Project < ActiveRecord::Base @ci_service ||= ci_services.select(&:activated?).first end + def avatar_type + unless self.avatar.image? + self.errors.add :avatar, 'only images allowed' + end + end + + def avatar_in_git + @avatar_file ||= 'logo.png' if repository.blob_at_branch('master', 'logo.png') + @avatar_file ||= 'logo.jpg' if repository.blob_at_branch('master', 'logo.jpg') + @avatar_file ||= 'logo.gif' if repository.blob_at_branch('master', 'logo.gif') + @avatar_file + end + + # For compatibility with old code + def code + path + end + def items_for(entity) case entity when 'issue' then @@ -358,7 +415,7 @@ class Project < ActiveRecord::Base end def team_member_by_name_or_email(name = nil, email = nil) - user = users.where("name like ? or email like ?", name, email).first + user = users.where('name like ? or email like ?', name, email).first project_members.where(user: user) if user end @@ -370,7 +427,7 @@ class Project < ActiveRecord::Base def name_with_namespace @name_with_namespace ||= begin if namespace - namespace.human_name + " / " + name + namespace.human_name + ' / ' + name else name end @@ -405,7 +462,7 @@ class Project < ActiveRecord::Base def valid_repo? repository.exists? rescue - errors.add(:path, "Invalid repository path") + errors.add(:path, 'Invalid repository path') false end @@ -464,7 +521,7 @@ class Project < ActiveRecord::Base end def http_url_to_repo - [gitlab_config.url, "/", path_with_namespace, ".git"].join('') + [gitlab_config.url, '/', path_with_namespace, '.git'].join('') end # Check if current branch name is marked as protected in the system @@ -527,6 +584,7 @@ class Project < ActiveRecord::Base # Since we do cache @event we need to reset cache in special cases: # * when project was moved # * when project was renamed + # * when the project avatar changes # Events cache stored like events/23-20130109142513. # The cache key includes updated_at timestamp. # Thus it will automatically generate a new fragment @@ -591,7 +649,7 @@ class Project < ActiveRecord::Base if gitlab_shell.add_repository(path_with_namespace) true else - errors.add(:base, "Failed to create repository") + errors.add(:base, 'Failed to create repository') false end end @@ -604,7 +662,7 @@ class Project < ActiveRecord::Base ProjectWiki.new(self, self.owner).wiki true rescue ProjectWiki::CouldNotCreateWikiError => ex - errors.add(:base, "Failed create wiki") + errors.add(:base, 'Failed create wiki') false end end diff --git a/app/models/project_contributions.rb b/app/models/project_contributions.rb new file mode 100644 index 00000000000..8ab2d814a94 --- /dev/null +++ b/app/models/project_contributions.rb @@ -0,0 +1,23 @@ +class ProjectContributions + attr_reader :project, :user + + def initialize(project, user) + @project, @user = project, user + end + + def commits_log + repository = project.repository + + if !repository.exists? || repository.empty? + return {} + end + + Rails.cache.fetch(cache_key) do + repository.commits_per_day_for_user(user) + end + end + + def cache_key + "#{Date.today.to_s}-commits-log-#{project.id}-#{user.email}" + end +end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index b9eec9ab21e..16e1b83da4b 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -1,3 +1,17 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + class BambooService < CiService include HTTParty 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..b6b79589f1d --- /dev/null +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -0,0 +1,52 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +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..25e399883b7 --- /dev/null +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -0,0 +1,39 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +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..acc8b33178c --- /dev/null +++ b/app/models/project_services/issue_tracker_service.rb @@ -0,0 +1,88 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +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..7a32b0e8c2c --- /dev/null +++ b/app/models/project_services/jira_service.rb @@ -0,0 +1,38 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +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..547b2401832 --- /dev/null +++ b/app/models/project_services/redmine_service.rb @@ -0,0 +1,38 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +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/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 52b5862e4d1..dca718b5e8c 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -1,3 +1,17 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + class TeamcityService < CiService include HTTParty diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 657ee23ae23..bc9c3ce58f6 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -160,7 +160,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids += group_members.pluck(:user_id) if group + user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) end diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 1b06dd77523..97207ba1272 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -2,11 +2,12 @@ # # Table name: protected_branches # -# id :integer not null, primary key -# project_id :integer not null -# name :string(255) not null -# created_at :datetime -# updated_at :datetime +# id :integer not null, primary key +# project_id :integer not null +# name :string(255) not null +# created_at :datetime +# updated_at :datetime +# developers_can_push :boolean default(FALSE), not null # class ProtectedBranch < ActiveRecord::Base diff --git a/app/models/repository.rb b/app/models/repository.rb index 93994123a90..4e45a6723b8 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -30,7 +30,7 @@ class Repository commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Commit.new(commit) if commit commit - rescue Rugged::OdbError => ex + rescue Rugged::OdbError nil end @@ -61,25 +61,25 @@ class Repository end def add_branch(branch_name, ref) - Rails.cache.delete(cache_key(:branch_names)) + cache.expire(:branch_names) gitlab_shell.add_branch(path_with_namespace, branch_name, ref) end def add_tag(tag_name, ref, message = nil) - Rails.cache.delete(cache_key(:tag_names)) + cache.expire(:tag_names) gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) end def rm_branch(branch_name) - Rails.cache.delete(cache_key(:branch_names)) + cache.expire(:branch_names) gitlab_shell.rm_branch(path_with_namespace, branch_name) end def rm_tag(tag_name) - Rails.cache.delete(cache_key(:tag_names)) + cache.expire(:tag_names) gitlab_shell.rm_tag(path_with_namespace, tag_name) end @@ -97,19 +97,15 @@ class Repository end def branch_names - Rails.cache.fetch(cache_key(:branch_names)) do - raw_repository.branch_names - end + cache.fetch(:branch_names) { raw_repository.branch_names } end def tag_names - Rails.cache.fetch(cache_key(:tag_names)) do - raw_repository.tag_names - end + cache.fetch(:tag_names) { raw_repository.tag_names } end def commit_count - Rails.cache.fetch(cache_key(:commit_count)) do + cache.fetch(:commit_count) do begin raw_repository.commit_count(self.root_ref) rescue @@ -121,26 +117,21 @@ class Repository # Return repo size in megabytes # Cached in redis def size - Rails.cache.fetch(cache_key(:size)) do - raw_repository.size - end + cache.fetch(:size) { raw_repository.size } end def expire_cache - Rails.cache.delete(cache_key(:size)) - Rails.cache.delete(cache_key(:branch_names)) - Rails.cache.delete(cache_key(:tag_names)) - Rails.cache.delete(cache_key(:commit_count)) - Rails.cache.delete(cache_key(:graph_log)) - Rails.cache.delete(cache_key(:readme)) - Rails.cache.delete(cache_key(:version)) - Rails.cache.delete(cache_key(:contribution_guide)) + %i(size branch_names tag_names commit_count graph_log + readme version contribution_guide).each do |key| + cache.expire(key) + end end def graph_log - Rails.cache.fetch(cache_key(:graph_log)) do + cache.fetch(:graph_log) do 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,14 +139,30 @@ 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 cache_key(type) - "#{type}:#{path_with_namespace}" + 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 method_missing(m, *args, &block) @@ -177,13 +184,11 @@ class Repository end def readme - Rails.cache.fetch(cache_key(:readme)) do - tree(:head).readme - end + cache.fetch(:readme) { tree(:head).readme } end def version - Rails.cache.fetch(cache_key(:version)) do + cache.fetch(:version) do tree(:head).blobs.find do |file| file.name.downcase == 'version' end @@ -191,9 +196,7 @@ class Repository end def contribution_guide - Rails.cache.fetch(cache_key(:contribution_guide)) do - tree(:head).contribution_guide - end + cache.fetch(:contribution_guide) { tree(:head).contribution_guide } end def head_commit @@ -312,4 +315,27 @@ class Repository [] end end + + def tag_names_contains(sha) + args = %W(git tag --contains #{sha}) + names = Gitlab::Popen.popen(args, path_to_repo).first + + if names.respond_to?(:split) + names = names.split("\n").map(&:strip) + + names.each do |name| + name.slice! '* ' + end + + names + else + [] + end + end + + private + + def cache + @cache ||= RepositoryCache.new(path_with_namespace) + end 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/models/user.rb b/app/models/user.rb index 743410c22e3..69fe674df83 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,8 +26,6 @@ # bio :string(255) # failed_attempts :integer default(0) # locked_at :datetime -# extern_uid :string(255) -# provider :string(255) # username :string(255) # can_create_group :boolean default(TRUE), not null # can_create_team :boolean default(TRUE), not null @@ -36,7 +34,6 @@ # notification_level :integer default(1), not null # password_expires_at :datetime # created_by_id :integer -# last_credential_check_at :datetime # avatar :string(255) # confirmation_token :string(255) # confirmed_at :datetime @@ -44,6 +41,8 @@ # unconfirmed_email :string(255) # hide_no_ssh_key :boolean default(FALSE) # website_url :string(255) default(""), not null +# last_credential_check_at :datetime +# github_access_token :string(255) # require 'carrierwave/orm/activerecord' @@ -298,8 +297,8 @@ class User < ActiveRecord::Base def authorized_projects @authorized_projects ||= begin project_ids = personal_projects.pluck(:id) - project_ids += groups_projects.pluck(:id) - project_ids += projects.pluck(:id).uniq + project_ids.push(*groups_projects.pluck(:id)) + project_ids.push(*projects.pluck(:id).uniq) Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC') end end @@ -497,7 +496,7 @@ class User < ActiveRecord::Base def avatar_url(size = nil) if avatar.present? - [gitlab_config.url, avatar.url].join("/") + [gitlab_config.url, avatar.url].join else GravatarService.new.execute(email, size) 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 5a89c5d2936..2fc63b9f4b7 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -242,7 +242,7 @@ class NotificationService users end - # Build a list of users based on group notifcation settings + # Build a list of users based on group notification settings def select_users_group_setting(project, project_members, global_setting, users_global_level_watch) uids = users_group_notification(project, Notification::N_WATCH) @@ -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/oauth2/access_token_validation_service.rb b/app/services/oauth2/access_token_validation_service.rb index 95283489753..5a3b94129f1 100644 --- a/app/services/oauth2/access_token_validation_service.rb +++ b/app/services/oauth2/access_token_validation_service.rb @@ -13,7 +13,7 @@ module Oauth2::AccessTokenValidationService elsif token.revoked? return REVOKED - elsif !self.sufficent_scope?(token, scopes) + elsif !self.sufficient_scope?(token, scopes) return INSUFFICIENT_SCOPE else @@ -24,7 +24,7 @@ module Oauth2::AccessTokenValidationService protected # True if the token's scope is a superset of required scopes, # or the required scopes is empty. - def sufficent_scope?(token, scopes) + def sufficient_scope?(token, scopes) if scopes.blank? # if no any scopes required, the scopes of token is sufficient. return true diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb new file mode 100644 index 00000000000..7408e09ed1e --- /dev/null +++ b/app/services/projects/autocomplete_service.rb @@ -0,0 +1,15 @@ +module Projects + class AutocompleteService < BaseService + def initialize(project) + @project = project + end + + def issues + @project.issues.opened.select([:iid, :title]) + end + + def merge_requests + @project.merge_requests.opened.select([:iid, :title]) + end + end +end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 31226b7504b..139de70114b 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -7,7 +7,7 @@ module Projects def execute @project = Project.new(params) - # Reset visibility levet if is not allowed to set it + # Reset visibility level if is not allowed to set it unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) @project.visibility_level = default_features.visibility_level end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 4930660055a..6b0d4aca3e1 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -14,6 +14,9 @@ module Projects project.name = @from_project.name project.path = @from_project.path project.creator = @current_user + if @from_project.avatar.present? && @from_project.avatar.image? + project.avatar = @from_project.avatar + end if namespace = @params[:namespace] project.namespace = namespace @@ -39,16 +42,16 @@ module Projects end #Now fork the repo unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path) - raise "forking failed in gitlab-shell" + raise 'forking failed in gitlab-shell' end project.ensure_satellite_exists end rescue => ex - project.errors.add(:base, "Fork transaction failed.") + project.errors.add(:base, 'Fork transaction failed.') project.destroy end else - project.errors.add(:base, "Invalid fork destination") + project.errors.add(:base, 'Invalid fork destination') end project 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/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml index 89ed5102754..76b95264fd8 100644 --- a/app/views/dashboard/_project.html.haml +++ b/app/views/dashboard/_project.html.haml @@ -1,6 +1,8 @@ = link_to project_path(project), class: dom_class(project) do .dash-project-access-icon = visibility_level_icon(project.visibility_level) + .dash-project-avatar + = project_icon(project.to_param, alt: '', class: 'avatar s24') %span.str-truncated %span.namespace-name - if project.namespace diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 304aa17eba8..0596738342f 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -3,8 +3,8 @@ .input-group = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' - if current_user.can_create_project? - .input-group-addon - = link_to new_project_path, class: "" do + .input-group-addon.dash-new-project + = link_to new_project_path do %strong New project %ul.well-list.dash-list diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 944441669e7..f60bcc72e1d 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -11,6 +11,8 @@ - @projects.each do |project| %li.my-project-row %h4.project-title + .project-avatar + = project_icon(project.to_param, alt: '', class: 'avatar s60') .project-access-icon = visibility_level_icon(project.visibility_level) = link_to project_path(project), class: dom_class(project) do 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/_projects.html.haml b/app/views/groups/_projects.html.haml index 2c65b3049e3..34221595fd7 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -14,6 +14,8 @@ = link_to project_path(project), class: dom_class(project) do .dash-project-access-icon = visibility_level_icon(project.visibility_level) + .dash-project-avatar + = project_icon(project.to_param, alt: '', class: 'avatar s24') %span.str-truncated %span.project-name = project.name 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/groups/show.html.haml b/app/views/groups/show.html.haml index 81f0e1dd2d8..484bebca2d8 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,6 +1,6 @@ .dashboard %div - = image_tag group_icon(@group.path), class: "avatar s90" + = image_tag group_icon(@group.path), class: "avatar avatar-tile s90" .clearfix %h2 = @group.name 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 - %i.fa.fa-cogs - %span - Settings - = nav_link(controller: :applications) do - = link_to admin_applications_path 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 + 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/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 96fe91b9b20..bc6f76a2661 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -20,7 +20,7 @@ = radio_button_tag :notification_level, Notification::N_MENTION, @notification.mention?, class: 'trigger-submit' .level-title Mention - %p You will receive notifications only for comments where you was @mentioned + %p You will receive notifications only for comments in which you were @mentioned .radio = label_tag nil, class: '' do 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/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml new file mode 100644 index 00000000000..02c9ef45f2b --- /dev/null +++ b/app/views/projects/_github_import_modal.html.haml @@ -0,0 +1,22 @@ +%div#github_import_modal.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 GitHub OAuth import + .modal-body + You need to setup integration with GitHub first. + = link_to 'How to setup integration with GitHub', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md' + + +:javascript + $(function(){ + var import_modal = $('#github_import_modal').modal({modal: true, show:false}); + $('.how_to_import_link').bind("click", function(e){ + e.preventDefault(); + import_modal.show(); + }); + $('.modal-header .close').bind("click", function(){ + import_modal.hide(); + }) + }) diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 30d063c7a36..2ed49f83a7a 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,5 +1,7 @@ - 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 - if @project.description.present? 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 index e7c3460ad78..e7c3460ad78 100644 --- a/app/views/projects/edit_tree/preview.html.haml +++ b/app/views/projects/blob/preview.html.haml diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index a6623240da1..2719bcc33bc 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -5,7 +5,7 @@ %h3.page-title %i.fa.fa-code-fork New branch -= form_tag project_branches_path, method: :post, class: "form-horizontal" do += form_tag project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal" do .form-group = label_tag :branch_name, 'Name for new branch', class: 'control-label' .col-sm-10 @@ -19,6 +19,7 @@ = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' :javascript + disableButtonIfAnyEmptyField($("#new-branch-form"), ".form-control", ".btn-create"); var availableTags = #{@project.repository.ref_names.to_json}; $("#ref").autocomplete({ diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index e149f017f84..b41fb1437f2 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -37,18 +37,23 @@ - @commit.parents.each do |parent| = link_to parent.short_id, project_commit_path(@project, parent) -- if @branches.any? - .commit-info-row - %span.cgray - Exists in +.commit-info-row + - if @branches.any? %span - branch = commit_default_branch(@project, @branches) - = link_to(branch, project_tree_path(@project, branch)) - - if @branches.any? - and in - = link_to("#{pluralize(@branches.count, "other branch")}", "#", class: "js-details-expand") + = link_to(project_tree_path(@project, branch)) do + %span.label.label-gray + %i.fa.fa-code-fork + = branch + - if @branches.any? || @tags.any? + = link_to("#", class: "js-details-expand") do + %span.label.label-gray + \... %span.js-details-content.hide - = commit_branches_links(@project, @branches) + - if @branches.any? + = commit_branches_links(@project, @branches) + - if @tags.any? + = commit_tags_links(@project, @tags) .commit-box %h3.commit-title diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 34d13502231..8d080f710d8 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -9,6 +9,9 @@ .diff-btn-group - if @commit.parent_ids.present? = view_file_btn(@commit.parent_id, diff_file, project) + - elsif diff_file.diff.submodule? + - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) + = submodule_link(submodule_item, @commit.id) - else - if diff_file.renamed_file %span= "#{diff_file.old_path} renamed to #{diff_file.new_path}" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f2bb56b5664..31bdbb562a1 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -7,7 +7,8 @@ %p.light Some settings, such as "Transfer Project", are hidden inside the danger area below. %hr .panel-body - = form_for @project, remote: true, html: { class: "edit_project form-horizontal" } do |f| + = form_for @project, remote: true, html: { multipart: true, class: "edit_project form-horizontal" }, authenticity_token: true do |f| + %fieldset .form-group.project_name_holder = f.label :name, class: 'control-label' do @@ -50,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 @@ -80,6 +72,32 @@ = f.check_box :snippets_enabled %span.descr Share code pastes with others out of git repository + %fieldset.features + %legend + Project avatar: + .form-group + .col-sm-2 + .col-sm-10 + - if @project.avatar? + = project_icon(@project.to_param, alt: '', class: 'avatar s160') + %p.light + - if @project.avatar_in_git + Project avatar in repository: #{ @project.avatar_in_git } + %p.light + - if @project.avatar? + You can change your project avatar here + - else + You can upload an project avatar here + %a.choose-btn.btn.btn-small.js-choose-project-avatar-button + %i.icon-paper-clip + %span Choose File ... + + %span.file_name.js-avatar-filename File name... + = f.file_field :avatar, class: "js-project-avatar-input hidden" + .light The maximum file size allowed is 200KB. + - if @project.avatar? + %hr + = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" .form-actions = f.submit 'Save changes', class: "btn btn-save" 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 59f19c8b7a3..36628195b4e 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,5 +1,19 @@ +- if current_user && can?(current_user, :download_code, @project) + = render 'shared/no_ssh' + = 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/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 010ca3b68b3..816851a8abe 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,6 +1,6 @@ .append-bottom-10 .check-all-holder - = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left" + = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left", disabled: !can?(current_user, :modify_issue, @project) = render 'shared/issuable_filter' .clearfix 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/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml index 941b15d3b32..ee7fd0ef157 100644 --- a/app/views/projects/merge_requests/show/_mr_ci.html.haml +++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml @@ -3,21 +3,21 @@ %i.fa.fa-check %span CI build passed for #{@merge_request.last_commit_short_sha}. - = link_to "Build page", ci_build_details_path(@merge_request) + = link_to "Build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" .ci_widget.ci-failed{style: "display:none"} %i.fa.fa-times %span CI build failed for #{@merge_request.last_commit_short_sha}. - = link_to "Build page", ci_build_details_path(@merge_request) + = link_to "Build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" - [:running, :pending].each do |status| .ci_widget{class: "ci-#{status}", style: "display:none"} %i.fa.fa-clock-o %span CI build #{status} for #{@merge_request.last_commit_short_sha}. - = link_to "Build page", ci_build_details_path(@merge_request) + = link_to "Build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" .ci_widget %i.fa.fa-spinner diff --git a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml index 4fe5935bcf3..9bf6a9d081c 100644 --- a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml +++ b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml @@ -12,6 +12,6 @@ Failed to remove source branch '#{@merge_request.source_branch}' .remove_source_branch_in_progress.hide - %i.fa.fa-refresh.fa-spin + %i.fa.fa-spinner.fa-spin Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded. diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index ccd02acd761..3e0f9cbd80b 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -40,13 +40,18 @@ The import will time out after 4 minutes. For big repositories, use a clone/push combination. For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"} - - if github_import_enabled? - .project-import.form-group - .col-sm-2 - .col-sm-10 + .project-import.form-group + .col-sm-2 + .col-sm-10 + - if github_import_enabled? = link_to status_github_import_path do %i.fa.fa-github Import projects from GitHub + - else + = link_to '#', class: 'how_to_import_link light' do + %i.fa.fa-github + Import projects from GitHub + = render 'github_import_modal' %hr.prepend-botton-10 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/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 691c169b620..88c7b7ccf1a 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -28,14 +28,24 @@ %span.note-last-update = note_timestamp(note) - - if note.upvote? - %span.vote.upvote.label.label-success - %i.fa.fa-thumbs-up - \+1 - - if note.downvote? - %span.vote.downvote.label.label-danger - %i.fa.fa-thumbs-down - \-1 + - if note.superceded?(@notes) + - if note.upvote? + %span.vote.upvote.label.label-gray.strikethrough + %i.fa.fa-thumbs-up + \+1 + - if note.downvote? + %span.vote.downvote.label.label-gray.strikethrough + %i.fa.fa-thumbs-down + \-1 + - else + - if note.upvote? + %span.vote.upvote.label.label-success + %i.fa.fa-thumbs-up + \+1 + - if note.downvote? + %span.vote.downvote.label.label-danger + %i.fa.fa-thumbs-down + \-1 .note-body diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml index 7271dd830ca..4604c0afd8d 100644 --- a/app/views/projects/services/index.html.haml +++ b/app/views/projects/services/index.html.haml @@ -1,13 +1,22 @@ %h3.page-title Project services %p.light Project services allow you to integrate GitLab with other applications -%hr -%ul.bordered-list +%table.table + %thead + %tr + %th + %th Service + %th Desription + %th Last edit - @services.sort_by(&:title).each do |service| - %li - %h4 + %tr + %td + = boolean_to_icon service.activated? + %td = link_to edit_project_service_path(@project, service.to_param) do - = service.title - .pull-right - = boolean_to_icon service.activated? - %p= service.description + %strong= service.title + %td + = service.description + %td.light + = time_ago_in_words service.updated_at + ago diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index af6e4567c1b..737a34decde 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -68,11 +68,11 @@ - @project.ci_services.each do |ci_service| - if ci_service.active? && ci_service.respond_to?(:builds_path) - if ci_service.respond_to?(:status_img_path) - = link_to ci_service.builds_path do + = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do = image_tag ci_service.status_img_path, alt: "build status" - else %span.light CI provided by - = link_to ci_service.title, ci_service.builds_path + = link_to ci_service.title, ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' - if readme .tab-pane#tab-readme diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index ad7ff8d3db8..289c52a2e3f 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -5,7 +5,7 @@ %h3.page-title %i.fa.fa-code-fork New tag -= form_tag project_tags_path, method: :post, class: "form-horizontal" do += form_tag project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal" do .form-group = label_tag :tag_name, 'Name for new tag', class: 'control-label' .col-sm-10 @@ -25,6 +25,7 @@ = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel' :javascript + disableButtonIfAnyEmptyField($("#new-tag-form"), ".form-control", ".btn-create"); var availableTags = #{@project.repository.ref_names.to_json}; $("#ref").autocomplete({ 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/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index 46e9be4af83..20c70cac699 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -1,14 +1,6 @@ -- tree, commit = submodule_links(submodule_item) %tr{ class: "tree-item" } %td.tree-item-file-name %i.fa.fa-archive - %span - = link_to truncate(submodule_item.name, length: 40), tree - @ - %span.monospace - - if commit.nil? - #{truncate_sha(submodule_item.id)} - - else - = link_to "#{truncate_sha(submodule_item.id)}", commit + = submodule_link(submodule_item, @ref) %td %td.hidden-xs 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/_groups.html.haml b/app/views/users/_groups.html.haml index ea008c2dede..32a1dc83b57 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,3 +1,4 @@ -- groups.each do |group| - = link_to group, class: 'profile-groups-avatars', :title => group.name do - - image_tag group_icon(group.path) +.clearfix + - groups.each do |group| + = link_to group, class: 'profile-groups-avatars', title: group.name do + = image_tag group_icon(group.path), class: 'avatar avatar-inline s40' diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml new file mode 100644 index 00000000000..13bdc5ed1e7 --- /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..b05918b019e 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,7 +1,7 @@ .row .col-md-8 %h3.page-title - = image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: '' + = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' = @user.name - if @user == current_user .pull-right @@ -15,11 +15,16 @@ .clearfix - if @groups.any? - %h4 Groups: + %h4 Groups = render 'groups', groups: @groups %hr + + .user-calendar + %h4.center.light + %i.fa.fa-spinner.fa-spin + %hr %h4 - User Activity: + User Activity - if current_user %span.rss-icon.pull-right @@ -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/application.rb b/config/application.rb index 8a280de6fac..24ba219cf3a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -12,11 +12,11 @@ module Gitlab # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/lib - #{config.root}/app/models/hooks - #{config.root}/app/models/concerns - #{config.root}/app/models/project_services - #{config.root}/app/models/members) + config.autoload_paths.push(*%W(#{config.root}/lib + #{config.root}/app/models/hooks + #{config.root}/app/models/concerns + #{config.root}/app/models/project_services + #{config.root}/app/models/members)) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. @@ -31,7 +31,7 @@ module Gitlab config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters += [:password] + config.filter_parameters.push(*[:password]) # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true @@ -70,7 +70,10 @@ module Gitlab config.middleware.use Rack::Cors do allow do origins '*' - resource '/api/*', headers: :any, methods: [:get, :post, :options, :put, :delete] + resource '/api/*', + headers: :any, + methods: [:get, :post, :options, :put, :delete], + expose: ['Link'] end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 92f601282e0..59af49c0180 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -60,7 +60,7 @@ production: &base ## Users can create accounts # This also allows normal users to sign up for accounts themselves - # default: false - By default GitLab administrators must create all new accounts + # default: true - By default users can sign up themselves # signup_enabled: true ## Standard login settings @@ -77,7 +77,7 @@ production: &base # This happens when the commit is pushed or merged into the default branch of a project. # When not specified the default issue_closing_pattern as specified below will be used. # Tip: you can test your closing pattern at http://rubular.com - # issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' + # issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' ## Default project features settings default_projects_features: @@ -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/initializers/1_settings.rb b/config/initializers/1_settings.rb index cdb958aa6a6..1ec842761ff 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -109,7 +109,7 @@ Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled']. Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil? diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index c6eb3e51036..79abe3c695d 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -145,7 +145,8 @@ Devise.setup do |config| # Time interval you can reset your password with a reset password key. # Don't put a too small interval or your users won't have the time to # change their passwords. - config.reset_password_within = 2.hours + # When someone else invites you to GitLab this time is also used so it should be pretty long. + config.reset_password_within = 2.days # ==> Configuration for :encryptable # Allow you to use another encryption algorithm besides bcrypt (default). You can use diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 23d9852725b..4819ab273dc 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -36,6 +36,12 @@ Doorkeeper.configure do # Issue access tokens with refresh token (disabled by default) use_refresh_token + # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled + # by default in non-development environments). OAuth2 delegates security in + # communication to the HTTPS protocol so it is wise to keep this enabled. + # + force_ssl_in_redirect_uri false + # Provide support for an owner to be assigned to each registered application (disabled by default) # Optional parameter :confirmation => true (default false) if you want to enforce ownership of # a registered application diff --git a/config/routes.rb b/config/routes.rb index ef3c5aedfcb..e122777314a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,8 +10,8 @@ Gitlab::Application.routes.draw do # # Search # - get 'search' => "search#show" - get 'search/autocomplete' => "search#autocomplete", as: :search_autocomplete + get 'search' => 'search#show' + get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete # API API::API.logger Rails.logger @@ -20,9 +20,9 @@ Gitlab::Application.routes.draw do # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys' , constraints: { username: /.*/ } - constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? } + constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? } constraints constraint do - mount Sidekiq::Web, at: "/admin/sidekiq", as: :sidekiq + mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end # Enable Grack support @@ -46,10 +46,10 @@ Gitlab::Application.routes.draw do # resources :snippets do member do - get "raw" + get 'raw' end end - get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ } + get '/s/:username' => 'snippets#user_index', as: :user_snippets, constraints: { username: /.*/ } # # Github importer area @@ -61,7 +61,7 @@ Gitlab::Application.routes.draw do end # - # Explroe area + # Explore area # namespace :explore do resources :projects, only: [:index] do @@ -72,12 +72,12 @@ Gitlab::Application.routes.draw do end resources :groups, only: [:index] - root to: "projects#trending" + root to: 'projects#trending' end # Compatibility with old routing - get 'public' => "explore/projects#index" - get 'public/projects' => "explore/projects#index" + get 'public' => 'explore/projects#index' + get 'public/projects' => 'explore/projects#index' # # Attachments serving @@ -122,7 +122,7 @@ Gitlab::Application.routes.draw do resource :application_settings, only: [:show, :update] - root to: "dashboard#index" + root to: 'dashboard#index' end # @@ -157,13 +157,16 @@ 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/ } # # Dashboard Area # - resource :dashboard, controller: "dashboard", only: [:show] do + resource :dashboard, controller: 'dashboard', only: [:show] do member do get :projects get :issues @@ -194,12 +197,12 @@ Gitlab::Application.routes.draw do devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions, confirmations: :confirmations } devise_scope :user do - get "/users/auth/:provider/omniauth_error" => "omniauth_callbacks#omniauth_error", as: :omniauth_error + get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error end # # Project Area # - resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do + resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: '/' do member do put :transfer post :archive @@ -211,16 +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 - resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new' + resource :avatar, only: [:show, :destroy] resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} resources :compare, only: [:index, :create] @@ -237,7 +244,7 @@ Gitlab::Application.routes.draw do resources :snippets, constraints: {id: /\d+/} do member do - get "raw" + get 'raw' end end @@ -249,7 +256,7 @@ Gitlab::Application.routes.draw do end member do - get "history" + get 'history' end end @@ -258,7 +265,7 @@ Gitlab::Application.routes.draw do resource :repository, only: [:show, :create] do member do - get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex } + get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } end end @@ -281,13 +288,13 @@ Gitlab::Application.routes.draw do resources :refs, only: [] do collection do - get "switch" + get 'switch' end member do # tree viewer logs - get "logs_tree", constraints: { id: Gitlab::Regex.git_reference_regex } - get "logs_tree/:path" => "refs#logs_tree", + get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + get 'logs_tree/:path' => 'refs#logs_tree', as: :logs_file, constraints: { id: Gitlab::Regex.git_reference_regex, @@ -353,10 +360,11 @@ Gitlab::Application.routes.draw do delete :delete_attachment end end + end end - get ':id' => "namespaces#show", constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} + get ':id' => 'namespaces#show', constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} - root to: "dashboard#show" + root to: 'dashboard#show' end diff --git a/db/migrate/20140125162722_add_avatar_to_projects.rb b/db/migrate/20140125162722_add_avatar_to_projects.rb new file mode 100644 index 00000000000..9523ac722f2 --- /dev/null +++ b/db/migrate/20140125162722_add_avatar_to_projects.rb @@ -0,0 +1,5 @@ +class AddAvatarToProjects < ActiveRecord::Migration + def change + add_column :projects, :avatar, :string + end +end 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 b453164d712..3f9ceb84e5d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150116234544) do +ActiveRecord::Schema.define(version: 20150116234545) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -327,6 +327,7 @@ ActiveRecord::Schema.define(version: 20150116234544) do t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" + t.string "avatar" end add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree @@ -433,6 +434,7 @@ ActiveRecord::Schema.define(version: 20150116234544) do t.string "website_url", default: "", null: false t.datetime "last_credential_check_at" 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/README.md b/doc/README.md index 3c8f8ad3d03..8c6d13e8506 100644 --- a/doc/README.md +++ b/doc/README.md @@ -9,7 +9,7 @@ - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. -- [Workflow](workflow/README.md) Learn how to get the maximum out of GitLab. +- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. ## Administrator documentation diff --git a/doc/api/groups.md b/doc/api/groups.md index e6893d71774..9217c7a7f24 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -20,7 +20,7 @@ GET /groups ] ``` -You can search for groups by name or path with: `/groups?search=Rails` +You can search for groups by name or path, see below. ## Details of a group @@ -73,6 +73,26 @@ Parameters: - `id` (required) - The ID of a user group +## Search for group + +Get all groups that match your string in their name or path. + +``` +GET /groups?search=foobar +``` + +```json +[ + { + "id": 1, + "name": "Foobar Group", + "path": "foo-bar", + "owner_id": 18, + "description": "An interesting group" + } +] +``` + ## Group members **Group access levels** diff --git a/doc/api/milestones.md b/doc/api/milestones.md index 2f525327504..d48b3bcce8a 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -72,3 +72,16 @@ Parameters: - `description` (optional) - The description of a milestone - `due_date` (optional) - The due date of the milestone - `state_event` (optional) - The state event of the milestone (close|activate) + +## Get all issues assigned to a single milestone + +Gets all issues assigned to a single project milestone. + +``` +GET /projects/:id/milestones/:milestone_id/issues +``` + +Parameters: + +- `id` (required) - The ID of a project +- `milestone_id` (required) - The ID of a project milestone diff --git a/doc/api/projects.md b/doc/api/projects.md index 027a8ec2e7f..d7804689c25 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -287,6 +287,31 @@ Parameters: - `visibility_level` (optional) - `import_url` (optional) +### Edit project + +Updates an existing project + +``` +PUT /projects/:id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `name` (optional) - project name +- `path` (optional) - repository name for project +- `description` (optional) - short project description +- `default_branch` (optional) +- `issues_enabled` (optional) +- `merge_requests_enabled` (optional) +- `wiki_enabled` (optional) +- `snippets_enabled` (optional) +- `public` (optional) - if `true` same as setting visibility_level = 20 +- `visibility_level` (optional) + +On success, method returns 200 with the updated project. If parameters are +invalid, 400 is returned. + ### Fork project Forks a project into the user namespace of the authenticated user. 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/development/shell_commands.md b/doc/development/shell_commands.md index 1e51ad73e32..42f17e19536 100644 --- a/doc/development/shell_commands.md +++ b/doc/development/shell_commands.md @@ -108,7 +108,7 @@ In other repositories, such as gitlab-shell you can also use `IO.popen`. ```ruby # Safe IO.popen example -logs = IO.popen(%W(git log), chdir: repo_dir).read +logs = IO.popen(%W(git log), chdir: repo_dir) { |p| p.read } ``` Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error. 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/ssh/ssh.md b/doc/ssh/ssh.md index d466c1bde72..f9ee627f1f5 100644 --- a/doc/ssh/ssh.md +++ b/doc/ssh/ssh.md @@ -18,4 +18,21 @@ Use the code below to show your public key. cat ~/.ssh/id_rsa.pub ``` -Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile. Please copy the complete key starting with `ssh-` and ending with your username and host. +Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile. Please copy the complete key starting with `ssh-` and ending with your username and host. + +Use code below to copy your public key to the clipboard. Depending on your OS you'll need to use a different command: + +**Windows:** +```bash +clip < ~/.ssh/id_rsa.pub +``` + +**Mac:** +```bash +pbcopy < ~/.ssh/id_rsa.pub +``` + +**Linux (requires xclip):** +```bash +xclip -sel clip < ~/.ssh/id_rsa.pub +``` 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/doc/update/6.x-or-7.x-to-7.7.md b/doc/update/6.x-or-7.x-to-7.7.md index 6501a8d2148..0cbea5d5997 100644 --- a/doc/update/6.x-or-7.x-to-7.7.md +++ b/doc/update/6.x-or-7.x-to-7.7.md @@ -89,6 +89,9 @@ sudo apt-get install logrotate # Install pkg-config and cmake, which is needed for the latest versions of rugged sudo apt-get install pkg-config cmake + +# Install Kerberos header files, which are needed for GitLab EE Kerberos support +sudo apt-get install libkrb5-dev ``` ## 5. Configure Redis to use sockets diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 1fe63274c29..33176aaba44 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -1,6 +1,6 @@ # Workflow -- [Workflow](workflow.md) +- [Feature branch workflow](workflow.md) - [Project Features](project_features.md) - [Authorization for merge requests](authorization_for_merge_requests.md) - [Groups](groups.md) diff --git a/doc/workflow/workflow.md b/doc/workflow/workflow.md index ab29cfb670b..f70e41df842 100644 --- a/doc/workflow/workflow.md +++ b/doc/workflow/workflow.md @@ -1,4 +1,4 @@ -# Workflow +# Feature branch workflow 1. Clone project: diff --git a/docker/Dockerfile b/docker/Dockerfile index 445fdd6d063..ec0923bd4c7 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.2-omnibus.5.4.2.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/project.feature b/features/project/project.feature index 7bb24e013a9..3e1fd54bee8 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -5,6 +5,19 @@ Feature: Project And project "Shop" has push event And I visit project "Shop" page + Scenario: I edit the project avatar + Given I visit edit project "Shop" page + When I change the project avatar + And I should see new project avatar + And I should see the "Remove avatar" button + + Scenario: I remove the project avatar + Given I visit edit project "Shop" page + And I have an project avatar + When I remove my project avatar + Then I should see the default project avatar + And I should not see the "Remove avatar" button + @javascript Scenario: I should see project activity When I visit project "Shop" page diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 6ea64f70092..ee8d0bffa9b 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 @tricky + 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/project.rb b/features/steps/project/project.rb index 5e7312d90ff..033d45e0253 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -17,17 +17,58 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'change project path settings' do - fill_in "project_path", with: "new-path" - click_button "Rename" + fill_in 'project_path', with: 'new-path' + click_button 'Rename' end step 'I should see project with new path settings' do - project.path.should == "new-path" + project.path.should == 'new-path' + end + + step 'I change the project avatar' do + attach_file( + :project_avatar, + File.join(Rails.root, 'public', 'gitlab_logo.png') + ) + click_button 'Save changes' + @project.reload + end + + step 'I should see new project avatar' do + @project.avatar.should be_instance_of AttachmentUploader + url = @project.avatar.url + url.should == "/uploads/project/avatar/#{ @project.id }/gitlab_logo.png" + end + + step 'I should see the "Remove avatar" button' do + page.should have_link('Remove avatar') + end + + step 'I have an project avatar' do + attach_file( + :project_avatar, + File.join(Rails.root, 'public', 'gitlab_logo.png') + ) + click_button 'Save changes' + @project.reload + end + + step 'I remove my project avatar' do + click_link 'Remove avatar' + @project.reload + end + + step 'I should see the default project avatar' do + @project.avatar?.should be_false + end + + step 'I should not see the "Remove avatar" button' do + page.should_not have_link('Remove avatar') end step 'I should see project "Shop" version' do within '.project-side' do - page.should have_content "Version: 6.7.0.pre" + page.should have_content 'Version: 6.7.0.pre' end end @@ -45,12 +86,12 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I should see project "Forum" README' do - page.should have_link "README.md" - page.should have_content "Sample repo for testing gitlab features" + page.should have_link 'README.md' + page.should have_content 'Sample repo for testing gitlab features' end step 'I should see project "Shop" README' do - page.should have_link "README.md" - page.should have_content "testme" + page.should have_link 'README.md' + page.should have_content 'testme' end end 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/milestones.rb b/lib/api/milestones.rb index 2ea49359df0..c5cd73943fb 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -75,6 +75,21 @@ module API render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400) end end + + # Get all issues for a single project milestone + # + # Parameters: + # id (required) - The ID of a project + # milestone_id (required) - The ID of a project milestone + # Example Request: + # GET /projects/:id/milestones/:milestone_id/issues + get ":id/milestones/:milestone_id/issues" do + authorize! :read_milestone, user_project + + @milestone = user_project.milestones.find(params[:milestone_id]) + present paginate(@milestone.issues), with: Entities::Issue + end + end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 5b0c31f1898..d96288bb982 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -200,6 +200,49 @@ module API end end + # Update an existing project + # + # Parameters: + # id (required) - the id of a project + # name (optional) - name of a project + # path (optional) - path of a project + # description (optional) - short project description + # issues_enabled (optional) + # merge_requests_enabled (optional) + # wiki_enabled (optional) + # snippets_enabled (optional) + # public (optional) - if true same as setting visibility_level = 20 + # visibility_level (optional) - visibility level of a project + # Example Request + # PUT /projects/:id + put ':id' do + attrs = attributes_for_keys [:name, + :path, + :description, + :default_branch, + :issues_enabled, + :merge_requests_enabled, + :wiki_enabled, + :snippets_enabled, + :public, + :visibility_level] + attrs = map_public_to_visibility_level(attrs) + authorize_admin_project + authorize! :rename_project, user_project if attrs[:name].present? + if attrs[:visibility_level].present? + authorize! :change_visibility_level, user_project + end + + ::Projects::UpdateService.new(user_project, + current_user, attrs).execute + + if user_project.valid? + present user_project, with: Entities::Project + else + render_validation_error!(user_project) + end + end + # Remove project # # Parameters: 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/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb index 401e6e047b1..a9fd59f03d9 100644 --- a/lib/gitlab/closing_issue_extractor.rb +++ b/lib/gitlab/closing_issue_extractor.rb @@ -3,14 +3,19 @@ module Gitlab ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern) def self.closed_by_message_in_project(message, project) - md = ISSUE_CLOSING_REGEX.match(message) - if md - extractor = Gitlab::ReferenceExtractor.new - extractor.analyze(md[0], project) - extractor.issues_for(project) - else - [] + issues = [] + + unless message.nil? + md = message.scan(ISSUE_CLOSING_REGEX) + + md.each do |ref| + extractor = Gitlab::ReferenceExtractor.new + extractor.analyze(ref[0], project) + issues += extractor.issues_for(project) + end end + + issues.uniq end end end diff --git a/lib/gitlab/commits_calendar.rb b/lib/gitlab/commits_calendar.rb new file mode 100644 index 00000000000..2f30d238e6b --- /dev/null +++ b/lib/gitlab/commits_calendar.rb @@ -0,0 +1,33 @@ +module Gitlab + class CommitsCalendar + attr_reader :timestamps + + def initialize(projects, user) + @timestamps = {} + date_timestamps = [] + + projects.reject(&:forked?).each do |project| + date_timestamps << ProjectContributions.new(project, user).commits_log + end + + # Sumarrize commits from all projects per days + 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 + + def starting_year + (Time.now - 1.year).strftime("%Y") + end + + def starting_month + Date.today.strftime("%m").to_i + end + end +end diff --git a/lib/gitlab/force_push_check.rb b/lib/gitlab/force_push_check.rb index 6a52cdba608..6ba2c3ad00a 100644 --- a/lib/gitlab/force_push_check.rb +++ b/lib/gitlab/force_push_check.rb @@ -4,7 +4,7 @@ module Gitlab return false if project.empty_repo? if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA - missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read + missed_refs, _ = Gitlab::Popen.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})) missed_refs.split("\n").size > 0 else false 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/lib/repository_cache.rb b/lib/repository_cache.rb new file mode 100644 index 00000000000..fa016a170cd --- /dev/null +++ b/lib/repository_cache.rb @@ -0,0 +1,21 @@ +# Interface to the Redis-backed cache store used by the Repository model +class RepositoryCache + attr_reader :namespace, :backend + + def initialize(namespace, backend = Rails.cache) + @namespace = namespace + @backend = backend + end + + def cache_key(type) + "#{type}:#{namespace}" + end + + def expire(key) + backend.delete(cache_key(key)) + end + + def fetch(key, &block) + backend.fetch(cache_key(key), &block) + end +end diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake index 507b315759d..4aefc18ce14 100644 --- a/lib/tasks/spinach.rake +++ b/lib/tasks/spinach.rake @@ -2,9 +2,15 @@ Rake::Task["spinach"].clear if Rake::Task.task_defined?('spinach') desc "GITLAB | Run spinach" task :spinach do + tags = if ENV['SEMAPHORE'] + '~@tricky' + else + '~@semaphore' + end + cmds = [ %W(rake gitlab:setup), - %W(spinach), + %W(spinach --tags #{tags}), ] run_commands(cmds) end 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/merge_requests.rb b/spec/factories/merge_requests.rb index 0ae8ea5f878..6ce1d7446fe 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -18,6 +18,7 @@ # iid :integer # description :text # position :integer default(0) +# locked_at :datetime # FactoryGirl.define do diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 60eb73e4a95..0899a7603fc 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -24,6 +24,9 @@ # import_status :string(255) # repository_size :float default(0.0) # star_count :integer default(0), not null +# import_type :string(255) +# import_source :string(255) +# avatar :string(255) # FactoryGirl.define do @@ -74,7 +77,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/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 9cdbc846b19..a46883b3c99 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -6,15 +6,15 @@ describe ApplicationHelper do controller.stub(:controller_name).and_return('foo') end - it "returns true when controller matches argument" do + it 'returns true when controller matches argument' do current_controller?(:foo).should be_true end - it "returns false when controller does not match argument" do + it 'returns false when controller does not match argument' do current_controller?(:bar).should_not be_true end - it "should take any number of arguments" do + it 'should take any number of arguments' do current_controller?(:baz, :bar).should_not be_true current_controller?(:baz, :bar, :foo).should be_true end @@ -25,109 +25,131 @@ describe ApplicationHelper do allow(self).to receive(:action_name).and_return('foo') end - it "returns true when action matches argument" do + it 'returns true when action matches argument' do current_action?(:foo).should be_true end - it "returns false when action does not match argument" do + it 'returns false when action does not match argument' do current_action?(:bar).should_not be_true end - it "should take any number of arguments" do + it 'should take any number of arguments' do current_action?(:baz, :bar).should_not be_true current_action?(:baz, :bar, :foo).should be_true end end - describe "group_icon" do + describe 'group_icon' do avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') - it "should return an url for the avatar" do + it 'should return an url for the avatar' do group = create(:group) group.avatar = File.open(avatar_file_path) group.save! group_icon(group.path).to_s.should match("/uploads/group/avatar/#{ group.id }/gitlab_logo.png") end - it "should give default avatar_icon when no avatar is present" do + it 'should give default avatar_icon when no avatar is present' do group = create(:group) group.save! - group_icon(group.path).should match("group_avatar.png") + group_icon(group.path).should match('group_avatar.png') end end - describe "avatar_icon" do + describe 'project_icon' do avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') - it "should return an url for the avatar" do + it 'should return an url for the avatar' do + project = create(:project) + project.avatar = File.open(avatar_file_path) + project.save! + project_icon(project.to_param).to_s.should == + "<img alt=\"Gitlab logo\" src=\"/uploads/project/avatar/#{ project.id }/gitlab_logo.png\" />" + end + + it 'should give uploaded icon when present' do + project = create(:project) + project.save! + + Project.any_instance.stub(:avatar_in_git).and_return(true) + + project_icon(project.to_param).to_s.should match( + image_tag(project_avatar_path(project))) + end + end + + describe 'avatar_icon' do + avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') + + it 'should return an url for the avatar' do user = create(:user) user.avatar = File.open(avatar_file_path) user.save! avatar_icon(user.email).to_s.should match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png") end - it "should return an url for the avatar with relative url" do - Gitlab.config.gitlab.stub(relative_url_root: "/gitlab") + it 'should return an url for the avatar with relative url' do + Gitlab.config.gitlab.stub(relative_url_root: '/gitlab') Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url)) user = create(:user) user.avatar = File.open(avatar_file_path) user.save! - avatar_icon(user.email).to_s.should match("/gitlab//uploads/user/avatar/#{ user.id }/gitlab_logo.png") + avatar_icon(user.email).to_s.should match("/gitlab/uploads/user/avatar/#{ user.id }/gitlab_logo.png") end - it "should call gravatar_icon when no avatar is present" do + it 'should call gravatar_icon when no avatar is present' do user = create(:user, email: 'test@example.com') user.save! - avatar_icon(user.email).to_s.should == "http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=40&d=identicon" + avatar_icon(user.email).to_s.should == 'http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=40&d=identicon' end end - describe "gravatar_icon" do + describe 'gravatar_icon' do let(:user_email) { 'user@email.com' } - it "should return a generic avatar path when Gravatar is disabled" do + it 'should return a generic avatar path when Gravatar is disabled' do ApplicationSetting.any_instance.stub(gravatar_enabled?: false) gravatar_icon(user_email).should match('no_avatar.png') end - it "should return a generic avatar path when email is blank" do + it 'should return a generic avatar path when email is blank' do gravatar_icon('').should match('no_avatar.png') end - it "should return default gravatar url" do + it 'should return default gravatar url' do Gitlab.config.gitlab.stub(https: false) gravatar_icon(user_email).should match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') end - it "should use SSL when appropriate" do + it 'should use SSL when appropriate' do Gitlab.config.gitlab.stub(https: true) gravatar_icon(user_email).should match('https://secure.gravatar.com') end - it "should return custom gravatar path when gravatar_url is set" do + it 'should return custom gravatar path when gravatar_url is set' do allow(self).to receive(:request).and_return(double(:ssl? => false)) Gitlab.config.gravatar.stub(:plain_url).and_return('http://example.local/?s=%{size}&hash=%{hash}') gravatar_icon(user_email, 20).should == 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118' end - it "should accept a custom size" do + it 'should accept a custom size' do allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email, 64).should match(/\?s=64/) end - it "should use default size when size is wrong" do + it 'should use default size when size is wrong' do allow(self).to receive(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email, nil).should match(/\?s=40/) end - it "should be case insensitive" do + it 'should be case insensitive' do allow(self).to receive(:request).and_return(double(:ssl? => false)) - gravatar_icon(user_email).should == gravatar_icon(user_email.upcase + " ") + gravatar_icon(user_email).should == gravatar_icon(user_email.upcase + ' ') end end - describe "grouped_options_refs" do + describe 'grouped_options_refs' do # Override Rails' grouped_options_for_select helper since HTML is harder to work with def grouped_options_for_select(options, *args) options @@ -140,17 +162,17 @@ describe ApplicationHelper do @project = create(:project) end - it "includes a list of branch names" do + it 'includes a list of branch names' do options[0][0].should == 'Branches' options[0][1].should include('master', 'feature') end - it "includes a list of tag names" do + it 'includes a list of tag names' do options[1][0].should == 'Tags' options[1][1].should include('v1.0.0','v1.1.0') end - it "includes a specific commit ref if defined" do + it 'includes a specific commit ref if defined' do # Must be an instance variable @ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8' @@ -158,26 +180,26 @@ describe ApplicationHelper do options[2][1].should == [@ref] end - it "sorts tags in a natural order" do + it 'sorts tags in a natural order' do # Stub repository.tag_names to make sure we get some valid testing data - expect(@project.repository).to receive(:tag_names).and_return(["v1.0.9", "v1.0.10", "v2.0", "v3.1.4.2", "v1.0.9a"]) + expect(@project.repository).to receive(:tag_names).and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v1.0.9a']) - options[1][1].should == ["v3.1.4.2", "v2.0", "v1.0.10", "v1.0.9a", "v1.0.9"] + options[1][1].should == ['v3.1.4.2', 'v2.0', 'v1.0.10', 'v1.0.9a', 'v1.0.9'] end end - describe "user_color_scheme_class" do - context "with current_user is nil" do - it "should return a string" do + describe 'user_color_scheme_class' do + context 'with current_user is nil' do + it 'should return a string' do allow(self).to receive(:current_user).and_return(nil) user_color_scheme_class.should be_kind_of(String) end end - context "with a current_user" do + context 'with a current_user' do (1..5).each do |color_scheme_id| context "with color_scheme_id == #{color_scheme_id}" do - it "should return a string" do + it 'should return a string' do current_user = double(:color_scheme_id => color_scheme_id) allow(self).to receive(:current_user).and_return(current_user) user_color_scheme_class.should be_kind_of(String) @@ -187,43 +209,43 @@ describe ApplicationHelper do end end - describe "simple_sanitize" do + describe 'simple_sanitize' do let(:a_tag) { '<a href="#">Foo</a>' } - it "allows the a tag" do + it 'allows the a tag' do simple_sanitize(a_tag).should == a_tag end - it "allows the span tag" do + it 'allows the span tag' do input = '<span class="foo">Bar</span>' simple_sanitize(input).should == input end - it "disallows other tags" do + it 'disallows other tags' do input = "<strike><b>#{a_tag}</b></strike>" simple_sanitize(input).should == a_tag end end - describe "link_to" do + describe 'link_to' do - it "should not include rel=nofollow for internal links" do - expect(link_to("Home", root_path)).to eq("<a href=\"/\">Home</a>") + it 'should not include rel=nofollow for internal links' do + expect(link_to('Home', root_path)).to eq("<a href=\"/\">Home</a>") end - it "should include rel=nofollow for external links" do - expect(link_to("Example", "http://www.example.com")).to eq("<a href=\"http://www.example.com\" rel=\"nofollow\">Example</a>") + it 'should include rel=nofollow for external links' do + expect(link_to('Example', 'http://www.example.com')).to eq("<a href=\"http://www.example.com\" rel=\"nofollow\">Example</a>") end - it "should include re=nofollow for external links and honor existing html_options" do + it 'should include re=nofollow for external links and honor existing html_options' do expect( - link_to("Example", "http://www.example.com", class: "toggle", data: {toggle: "dropdown"}) + link_to('Example', 'http://www.example.com', class: 'toggle', data: {toggle: 'dropdown'}) ).to eq("<a class=\"toggle\" data-toggle=\"dropdown\" href=\"http://www.example.com\" rel=\"nofollow\">Example</a>") end - it "should include rel=nofollow for external links and preserver other rel values" do + it 'should include rel=nofollow for external links and preserver other rel values' do expect( - link_to("Example", "http://www.example.com", rel: "noreferrer") + link_to('Example', 'http://www.example.com', rel: 'noreferrer') ).to eq("<a href=\"http://www.example.com\" rel=\"noreferrer nofollow\">Example</a>") end end 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/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb index 31ecdacf28e..dcc3318e4f9 100644 --- a/spec/helpers/notifications_helper_spec.rb +++ b/spec/helpers/notifications_helper_spec.rb @@ -1,6 +1,9 @@ require 'spec_helper' describe NotificationsHelper do + include FontAwesome::Rails::IconHelper + include IconsHelper + describe 'notification_icon' do let(:notification) { double(disabled?: false, participating?: false, watch?: false) } 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 == - "<option value=\"redmine\">Redmine</option>\n" \ - "<option value=\"gitlab\">GitLab</option>" - end - - it "returns the correct issues trackers available with current tracker 'gitlab' selected" do - project_issues_trackers('gitlab').should == - "<option value=\"redmine\">Redmine</option>\n" \ - "<option selected=\"selected\" value=\"gitlab\">GitLab</option>" - end - - it "returns the correct issues trackers available with current tracker 'redmine' selected" do - project_issues_trackers('redmine').should == - "<option selected=\"selected\" value=\"redmine\">Redmine</option>\n" \ - "<option value=\"gitlab\">GitLab</option>" - 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/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb new file mode 100644 index 00000000000..867455daf23 --- /dev/null +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe Gitlab::ClosingIssueExtractor do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + let(:iid1) { issue.iid } + + describe :closed_by_message_in_project do + context 'with a single reference' do + it do + message = "Awesome commit (Closes ##{iid1})" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Awesome commit (closes ##{iid1})" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Closed ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "closed ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Awesome commit (fixes ##{iid1})" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Awesome commit (fix ##{iid1})" + subject.closed_by_message_in_project(message, project).should == [issue] + end + end + + context 'with multiple references' do + let(:other_issue) { create(:issue, project: project) } + let(:third_issue) { create(:issue, project: project) } + let(:iid2) { other_issue.iid } + let(:iid3) { third_issue.iid } + + it 'fetches issues in single line message' do + message = "Closes ##{iid1} and fix ##{iid2}" + + subject.closed_by_message_in_project(message, project). + should == [issue, other_issue] + end + + it 'fetches comma-separated issues references in single line message' do + message = "Closes ##{iid1}, closes ##{iid2}" + + subject.closed_by_message_in_project(message, project). + should == [issue, other_issue] + end + + it 'fetches comma-separated issues numbers in single line message' do + message = "Closes ##{iid1}, ##{iid2} and ##{iid3}" + + subject.closed_by_message_in_project(message, project). + should == [issue, other_issue, third_issue] + end + + it 'fetches issues in multi-line message' do + message = "Awesome commit (closes ##{iid1})\nAlso fixes ##{iid2}" + + subject.closed_by_message_in_project(message, project). + should == [issue, other_issue] + end + + it 'fetches issues in hybrid message' do + message = "Awesome commit (closes ##{iid1})\n"\ + "Also fixing issues ##{iid2}, ##{iid3} and #4" + + subject.closed_by_message_in_project(message, project). + should == [issue, other_issue, third_issue] + end + 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/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb new file mode 100644 index 00000000000..af399f3a731 --- /dev/null +++ b/spec/lib/repository_cache_spec.rb @@ -0,0 +1,34 @@ +require 'rspec' +require_relative '../../lib/repository_cache' + +describe RepositoryCache do + let(:backend) { double('backend').as_null_object } + let(:cache) { RepositoryCache.new('example', backend) } + + describe '#cache_key' do + it 'includes the namespace' do + expect(cache.cache_key(:foo)).to eq 'foo:example' + end + end + + describe '#expire' do + it 'expires the given key from the cache' do + cache.expire(:foo) + expect(backend).to have_received(:delete).with('foo:example') + end + end + + describe '#fetch' do + it 'fetches the given key from the cache' do + cache.fetch(:bar) + expect(backend).to have_received(:fetch).with('bar:example') + end + + it 'accepts a block' do + p = -> {} + + cache.fetch(:baz, &p) + expect(backend).to have_received(:fetch).with('baz:example', &p) + end + end +end diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb index a3c353d5eab..a88a10d927f 100644 --- a/spec/lib/votes_spec.rb +++ b/spec/lib/votes_spec.rb @@ -20,11 +20,17 @@ describe Issue, 'Votes' do issue.upvotes.should == 1 end - it "should recognize multiple +1 notes" do - add_note "+1 This is awesome" - add_note "+1 I want this" + it 'should recognize multiple +1 notes' do + add_note '+1 This is awesome', create(:user) + add_note '+1 I want this', create(:user) issue.upvotes.should == 2 end + + it 'should not count 2 +1 votes from the same user' do + add_note '+1 This is awesome' + add_note '+1 I want this' + issue.upvotes.should == 1 + end end describe "#downvotes" do @@ -45,8 +51,8 @@ describe Issue, 'Votes' do end it "should recognize multiple -1 notes" do - add_note "-1 This is bad" - add_note "-1 Away with this" + add_note('-1 This is bad', create(:user)) + add_note('-1 Away with this', create(:user)) issue.downvotes.should == 2 end end @@ -73,11 +79,17 @@ describe Issue, 'Votes' do end it "should recognize multiple notes" do - add_note "+1 This is awesome" - add_note "-1 This is bad" - add_note "+1 I want this" + add_note('+1 This is awesome', create(:user)) + add_note('-1 This is bad', create(:user)) + add_note('+1 I want this', create(:user)) issue.votes_count.should == 3 end + + it 'should not count 2 -1 votes from the same user' do + add_note '-1 This is suspicious' + add_note '-1 This is bad' + issue.votes_count.should == 1 + end end describe "#upvotes_in_percent" do @@ -90,17 +102,17 @@ describe Issue, 'Votes' do issue.upvotes_in_percent.should == 100 end - it "should count multiple +1 notes as 100%" do - add_note "+1 This is awesome" - add_note "+1 I want this" + it 'should count multiple +1 notes as 100%' do + add_note('+1 This is awesome', create(:user)) + add_note('+1 I want this', create(:user)) issue.upvotes_in_percent.should == 100 end - it "should count fractions for multiple +1 and -1 notes correctly" do - add_note "+1 This is awesome" - add_note "+1 I want this" - add_note "-1 This is bad" - add_note "+1 me too" + it 'should count fractions for multiple +1 and -1 notes correctly' do + add_note('+1 This is awesome', create(:user)) + add_note('+1 I want this', create(:user)) + add_note('-1 This is bad', create(:user)) + add_note('+1 me too', create(:user)) issue.upvotes_in_percent.should == 75 end end @@ -115,22 +127,59 @@ describe Issue, 'Votes' do issue.downvotes_in_percent.should == 100 end - it "should count multiple -1 notes as 100%" do - add_note "-1 This is bad" - add_note "-1 Away with this" + it 'should count multiple -1 notes as 100%' do + add_note('-1 This is bad', create(:user)) + add_note('-1 Away with this', create(:user)) issue.downvotes_in_percent.should == 100 end - it "should count fractions for multiple +1 and -1 notes correctly" do - add_note "+1 This is awesome" - add_note "+1 I want this" - add_note "-1 This is bad" - add_note "+1 me too" + it 'should count fractions for multiple +1 and -1 notes correctly' do + add_note('+1 This is awesome', create(:user)) + add_note('+1 I want this', create(:user)) + add_note('-1 This is bad', create(:user)) + add_note('+1 me too', create(:user)) issue.downvotes_in_percent.should == 25 end end - def add_note(text) - issue.notes << create(:note, note: text, project: issue.project) + describe '#filter_superceded_votes' do + + it 'should count a users vote only once amongst multiple votes' do + add_note('-1 This needs work before I will accept it') + add_note('+1 I want this', create(:user)) + add_note('+1 This is is awesome', create(:user)) + add_note('+1 this looks good now') + add_note('+1 This is awesome', create(:user)) + add_note('+1 me too', create(:user)) + issue.downvotes.should == 0 + issue.upvotes.should == 5 + end + + it 'should count each users vote only once' do + add_note '-1 This needs work before it will be accepted' + add_note '+1 I like this' + add_note '+1 I still like this' + add_note '+1 I really like this' + add_note '+1 Give me this now!!!!' + issue.downvotes.should == 0 + issue.upvotes.should == 1 + end + + it 'should count a users vote only once without caring about comments' do + add_note '-1 This needs work before it will be accepted' + add_note 'Comment 1' + add_note 'Another comment' + add_note '+1 vote' + add_note 'final comment' + issue.downvotes.should == 0 + issue.upvotes.should == 1 + end + + end + + def add_note(text, author = issue.author) + created_at = Time.now - 1.hour + Note.count.seconds + issue.notes << create(:note, note: text, project: issue.project, + author_id: author.id, created_at: created_at) end end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 039775dddda..1723eba9ec3 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: application_settings +# +# id :integer not null, primary key +# default_projects_limit :integer +# signup_enabled :boolean +# signin_enabled :boolean +# gravatar_enabled :boolean +# sign_in_text :text +# created_at :datetime +# updated_at :datetime +# home_page_url :string(255) +# + require 'spec_helper' describe ApplicationSetting, models: true do diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index a6ec44da4be..7a2a7a4ce9b 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -57,16 +57,12 @@ eos let(:other_issue) { create :issue, project: other_project } it 'detects issues that this commit is marked as closing' do - stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX', - /Fixes #\d+/) commit.stub(safe_message: "Fixes ##{issue.iid}") commit.closes_issues(project).should == [issue] end it 'does not detect issues from other projects' do ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}" - stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX', - /^([Cc]loses|[Ff]ixes)/) commit.stub(safe_message: "Fixes #{ext_ref}") commit.closes_issues(project).should be_empty end diff --git a/spec/models/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 4e0d50d7f3f..4e0d50d7f3f 100644 --- a/spec/models/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb diff --git a/spec/models/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index 6ec82438dfe..6ec82438dfe 100644 --- a/spec/models/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb diff --git a/spec/models/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 4ab5261dc9d..8deb732de9c 100644 --- a/spec/models/system_hook_spec.rb +++ b/spec/models/hooks/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/models/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index e9c04ee89cb..e9c04ee89cb 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb diff --git a/spec/models/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 38657de6793..38657de6793 100644 --- a/spec/models/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb diff --git a/spec/models/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 9b5f89b6d7d..9b5f89b6d7d 100644 --- a/spec/models/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb diff --git a/spec/models/members_spec.rb b/spec/models/members_spec.rb index 6866c4794c2..cea653ec285 100644 --- a/spec/models/members_spec.rb +++ b/spec/models/members_spec.rb @@ -10,7 +10,7 @@ describe Member do it { should validate_presence_of(:user) } it { should validate_presence_of(:source) } - it { should ensure_inclusion_of(:access_level).in_array(Gitlab::Access.values) } + it { should validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) } end describe "Delegate methods" do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 7b0d261d72f..9585cf09768 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -18,6 +18,7 @@ # iid :integer # description :text # position :integer default(0) +# locked_at :datetime # require 'spec_helper' diff --git a/spec/models/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index 005dd41fea9..005dd41fea9 100644 --- a/spec/models/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb diff --git a/spec/models/buildbox_service_spec.rb b/spec/models/project_services/buildbox_service_spec.rb index 1d9ca51be16..1d9ca51be16 100644 --- a/spec/models/buildbox_service_spec.rb +++ b/spec/models/project_services/buildbox_service_spec.rb diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index ac156719b43..ac156719b43 100644 --- a/spec/models/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb diff --git a/spec/models/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 2c560c11dac..2c560c11dac 100644 --- a/spec/models/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb index 83277058fbb..83277058fbb 100644 --- a/spec/models/gitlab_ci_service_spec.rb +++ b/spec/models/project_services/gitlab_ci_service_spec.rb diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb new file mode 100644 index 00000000000..99ca04eff6e --- /dev/null +++ b/spec/models/project_services/jira_service_spec.rb @@ -0,0 +1,97 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +require 'spec_helper' + +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/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index f2813d66c7d..f2813d66c7d 100644 --- a/spec/models/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb diff --git a/spec/models/slack_message_spec.rb b/spec/models/project_services/slack_message_spec.rb index c530fad619b..c530fad619b 100644 --- a/spec/models/slack_message_spec.rb +++ b/spec/models/project_services/slack_message_spec.rb diff --git a/spec/models/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 34594072409..34594072409 100644 --- a/spec/models/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 70a15cac1a8..4669a9fd87d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -24,12 +24,15 @@ # import_status :string(255) # repository_size :float default(0.0) # star_count :integer default(0), not null +# import_type :string(255) +# import_source :string(255) +# avatar :string(255) # require 'spec_helper' describe Project do - describe "Associations" do + describe 'Associations' do it { should belong_to(:group) } it { should belong_to(:namespace) } it { should belong_to(:creator).class_name('User') } @@ -50,10 +53,10 @@ describe Project do it { should have_one(:pushover_service).dependent(:destroy) } end - describe "Mass assignment" do + describe 'Mass assignment' do end - describe "Validation" do + describe 'Validation' do let!(:project) { create(:project) } it { should validate_presence_of(:name) } @@ -68,7 +71,7 @@ describe Project do it { should ensure_length_of(:issues_tracker_id).is_within(0..255) } it { should validate_presence_of(:namespace) } - it "should not allow new projects beyond user limits" do + it 'should not allow new projects beyond user limits' do project2 = build(:project) project2.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) project2.should_not be_valid @@ -76,7 +79,7 @@ describe Project do end end - describe "Respond to" do + describe 'Respond to' do it { should respond_to(:url_to_repo) } it { should respond_to(:repo_exists?) } it { should respond_to(:satellite) } @@ -87,27 +90,27 @@ describe Project do it { should respond_to(:path_with_namespace) } end - it "should return valid url to repo" do - project = Project.new(path: "somewhere") - project.url_to_repo.should == Gitlab.config.gitlab_shell.ssh_path_prefix + "somewhere.git" + it 'should return valid url to repo' do + project = Project.new(path: 'somewhere') + project.url_to_repo.should == Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git' end - it "returns the full web URL for this repo" do - project = Project.new(path: "somewhere") + it 'returns the full web URL for this repo' do + project = Project.new(path: 'somewhere') project.web_url.should == "#{Gitlab.config.gitlab.url}/somewhere" end - it "returns the web URL without the protocol for this repo" do - project = Project.new(path: "somewhere") - project.web_url_without_protocol.should == "#{Gitlab.config.gitlab.url.split("://")[1]}/somewhere" + it 'returns the web URL without the protocol for this repo' do + project = Project.new(path: 'somewhere') + project.web_url_without_protocol.should == "#{Gitlab.config.gitlab.url.split('://')[1]}/somewhere" end - describe "last_activity methods" do + describe 'last_activity methods' do let(:project) { create(:project) } let(:last_event) { double(created_at: Time.now) } - describe "last_activity" do - it "should alias last_activity to last_event" do + describe 'last_activity' do + it 'should alias last_activity to last_event' do project.stub(last_event: last_event) project.last_activity.should == last_event end @@ -132,13 +135,13 @@ describe Project do let(:prev_commit_id) { merge_request.commits.last.id } let(:commit_id) { merge_request.commits.first.id } - it "should close merge request if last commit from source branch was pushed to target branch" do + it 'should close merge request if last commit from source branch was pushed to target branch' do project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.target_branch}", key.user) merge_request.reload merge_request.merged?.should be_true end - it "should update merge request commits with new one if pushed to source branch" do + it 'should update merge request commits with new one if pushed to source branch' do project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user) merge_request.reload merge_request.last_commit.id.should == commit_id @@ -164,14 +167,14 @@ describe Project do @project = create(:project, name: 'gitlabhq', namespace: @group) end - it { @project.to_param.should == "gitlab/gitlabhq" } + it { @project.to_param.should == 'gitlab/gitlabhq' } end end describe :repository do let(:project) { create(:project) } - it "should return valid repo" do + it 'should return valid repo' do project.repository.should be_kind_of(Repository) end end @@ -182,29 +185,29 @@ describe Project do let(:not_existed_issue) { create(:issue) } let(:ext_project) { create(:redmine_project) } - it "should be true or if used internal tracker and issue exists" do + it 'should be true or if used internal tracker and issue exists' do project.issue_exists?(existed_issue.iid).should be_true end - it "should be false or if used internal tracker and issue not exists" do + it 'should be false or if used internal tracker and issue not exists' do project.issue_exists?(not_existed_issue.iid).should be_false end - it "should always be true if used other tracker" do + it 'should always be true if used other tracker' do ext_project.issue_exists?(rand(100)).should be_true 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 + 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 + ext_project.default_issues_tracker?.should be_false end end @@ -212,15 +215,15 @@ describe Project do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } - it "should be true for projects with external issues tracker if issues enabled" do + it 'should be true for projects with external issues tracker if issues enabled' do ext_project.can_have_issues_tracker_id?.should be_true end - it "should be false for projects with internal issue tracker if issues enabled" do + it 'should be false for projects with internal issue tracker if issues enabled' do project.can_have_issues_tracker_id?.should be_false end - it "should be always false if issues disabled" do + it 'should be always false if issues disabled' do project.issues_enabled = false ext_project.issues_enabled = false @@ -308,4 +311,18 @@ describe Project do expect(project.star_count).to eq(0) end end + + describe :avatar_type do + let(:project) { create(:project) } + + it 'should be true if avatar is image' do + project.update_attribute(:avatar, 'uploads/avatar.png') + project.avatar_type.should be_true + end + + it 'should be false if avatar is html page' do + project.update_attribute(:avatar, 'uploads/avatar.html') + project.avatar_type.should == ['only images allowed'] + end + end end diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index af48c2c6d9e..b0f57e8a206 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -2,11 +2,12 @@ # # Table name: protected_branches # -# id :integer not null, primary key -# project_id :integer not null -# name :string(255) not null -# created_at :datetime -# updated_at :datetime +# id :integer not null, primary key +# project_id :integer not null +# name :string(255) not null +# created_at :datetime +# updated_at :datetime +# developers_can_push :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8be7f733a5b..83341e516a5 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -26,8 +26,6 @@ # bio :string(255) # failed_attempts :integer default(0) # locked_at :datetime -# extern_uid :string(255) -# provider :string(255) # username :string(255) # can_create_group :boolean default(TRUE), not null # can_create_team :boolean default(TRUE), not null @@ -36,7 +34,6 @@ # notification_level :integer default(1), not null # password_expires_at :datetime # created_by_id :integer -# last_credential_check_at :datetime # avatar :string(255) # confirmation_token :string(255) # confirmed_at :datetime @@ -44,6 +41,8 @@ # unconfirmed_email :string(255) # hide_no_ssh_key :boolean default(FALSE) # website_url :string(255) default(""), not null +# last_credential_check_at :datetime +# github_access_token :string(255) # require 'spec_helper' diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index f0619a1c801..647033309bd 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -8,48 +8,48 @@ describe API::API, api: true do before { project.team << [user, :developer] } - describe "GET /projects/:id/milestones" do - it "should return project milestones" do + describe 'GET /projects/:id/milestones' do + it 'should return project milestones' do get api("/projects/#{project.id}/milestones", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == milestone.title end - it "should return a 401 error if user not authenticated" do + it 'should return a 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones") response.status.should == 401 end end - describe "GET /projects/:id/milestones/:milestone_id" do - it "should return a project milestone by id" do + describe 'GET /projects/:id/milestones/:milestone_id' do + it 'should return a project milestone by id' do get api("/projects/#{project.id}/milestones/#{milestone.id}", user) response.status.should == 200 json_response['title'].should == milestone.title json_response['iid'].should == milestone.iid end - it "should return 401 error if user not authenticated" do + it 'should return 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}") response.status.should == 401 end - it "should return a 404 error if milestone id not found" do + it 'should return a 404 error if milestone id not found' do get api("/projects/#{project.id}/milestones/1234", user) response.status.should == 404 end end - describe "POST /projects/:id/milestones" do - it "should create a new project milestone" do + describe 'POST /projects/:id/milestones' do + it 'should create a new project milestone' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone' response.status.should == 201 json_response['title'].should == 'new milestone' json_response['description'].should be_nil end - it "should create a new project milestone with description and due date" do + it 'should create a new project milestone with description and due date' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone', description: 'release', due_date: '2013-03-02' response.status.should == 201 @@ -57,29 +57,29 @@ describe API::API, api: true do json_response['due_date'].should == '2013-03-02' end - it "should return a 400 error if title is missing" do + it 'should return a 400 error if title is missing' do post api("/projects/#{project.id}/milestones", user) response.status.should == 400 end end - describe "PUT /projects/:id/milestones/:milestone_id" do - it "should update a project milestone" do + describe 'PUT /projects/:id/milestones/:milestone_id' do + it 'should update a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), title: 'updated title' response.status.should == 200 json_response['title'].should == 'updated title' end - it "should return a 404 error if milestone id not found" do + it 'should return a 404 error if milestone id not found' do put api("/projects/#{project.id}/milestones/1234", user), title: 'updated title' response.status.should == 404 end end - describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do - it "should update a project milestone" do + describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do + it 'should update a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' response.status.should == 200 @@ -88,12 +88,29 @@ describe API::API, api: true do end end - describe "PUT /projects/:id/milestones/:milestone_id to test observer on close" do - it "should create an activity event when an milestone is closed" do + describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do + it 'should create an activity event when an milestone is closed' do Event.should_receive(:create) put api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' end end + + describe 'GET /projects/:id/milestones/:milestone_id/issues' do + before do + milestone.issues << create(:issue) + end + it 'should return project issues for a particular milestone' do + get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['milestone']['title'].should == milestone.title + end + + it 'should return a 401 error if user not authenticated' do + get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") + response.status.should == 401 + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 3098b0f77f9..dc410107410 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- require 'spec_helper' describe API::API, api: true do @@ -12,43 +13,67 @@ describe API::API, api: true do let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) } + let(:user4) { create(:user) } + let(:project3) do + create(:project, + name: 'second_project', + path: 'second_project', + creator_id: user.id, + namespace: user.namespace, + merge_requests_enabled: false, + issues_enabled: false, wiki_enabled: false, + snippets_enabled: false, visibility_level: 0) + end + let(:project_member3) do + create(:project_member, + user: user4, + project: project3, + access_level: ProjectMember::MASTER) + end + let(:project4) do + create(:project, + name: 'third_project', + path: 'third_project', + creator_id: user4.id, + namespace: user4.namespace) + end - describe "GET /projects" do + describe 'GET /projects' do before { project } - context "when unauthenticated" do - it "should return authentication error" do - get api("/projects") + context 'when unauthenticated' do + it 'should return authentication error' do + get api('/projects') response.status.should == 401 end end - context "when authenticated" do - it "should return an array of projects" do - get api("/projects", user) + context 'when authenticated' do + it 'should return an array of projects' do + get api('/projects', user) response.status.should == 200 json_response.should be_an Array json_response.first['name'].should == project.name json_response.first['owner']['username'].should == user.username end - context "and using search" do - it "should return searched project" do - get api("/projects", user), { search: project.name } + context 'and using search' do + it 'should return searched project' do + get api('/projects', user), { search: project.name } response.status.should eq(200) json_response.should be_an Array json_response.length.should eq(1) end end - context "and using sorting" do + context 'and using sorting' do before do project2 project3 end - it "should return the correct order when sorted by id" do - get api("/projects", user), { order_by: 'id', sort: 'desc'} + it 'should return the correct order when sorted by id' do + get api('/projects', user), { order_by: 'id', sort: 'desc'} response.status.should eq(200) json_response.should be_an Array json_response.first['id'].should eq(project3.id) @@ -57,26 +82,26 @@ describe API::API, api: true do end end - describe "GET /projects/all" do + describe 'GET /projects/all' do before { project } - context "when unauthenticated" do - it "should return authentication error" do - get api("/projects/all") + context 'when unauthenticated' do + it 'should return authentication error' do + get api('/projects/all') response.status.should == 401 end end - context "when authenticated as regular user" do - it "should return authentication error" do - get api("/projects/all", user) + context 'when authenticated as regular user' do + it 'should return authentication error' do + get api('/projects/all', user) response.status.should == 403 end end - context "when authenticated as admin" do - it "should return an array of all projects" do - get api("/projects/all", admin) + context 'when authenticated as admin' do + it 'should return an array of all projects' do + get api('/projects/all', admin) response.status.should == 200 json_response.should be_an Array project_name = project.name @@ -92,59 +117,59 @@ describe API::API, api: true do end end - describe "POST /projects" do - context "maximum number of projects reached" do + describe 'POST /projects' do + context 'maximum number of projects reached' do before do (1..user2.projects_limit).each do |project| - post api("/projects", user2), name: "foo#{project}" + post api('/projects', user2), name: "foo#{project}" end end - it "should not create new project" do + it 'should not create new project' do expect { - post api("/projects", user2), name: 'foo' + post api('/projects', user2), name: 'foo' }.to change {Project.count}.by(0) end end - it "should create new project without path" do - expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1) + it 'should create new project without path' do + expect { post api('/projects', user), name: 'foo' }.to change {Project.count}.by(1) end - it "should not create new project without name" do - expect { post api("/projects", user) }.to_not change {Project.count} + it 'should not create new project without name' do + expect { post api('/projects', user) }.to_not change {Project.count} end - it "should return a 400 error if name not given" do - post api("/projects", user) + it 'should return a 400 error if name not given' do + post api('/projects', user) response.status.should == 400 end - it "should create last project before reaching project limit" do - (1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" } - post api("/projects", user2), name: "foo" + it 'should create last project before reaching project limit' do + (1..user2.projects_limit-1).each { |p| post api('/projects', user2), name: "foo#{p}" } + post api('/projects', user2), name: 'foo' response.status.should == 201 end - it "should respond with 201 on success" do - post api("/projects", user), name: 'foo' + it 'should respond with 201 on success' do + post api('/projects', user), name: 'foo' response.status.should == 201 end - it "should respond with 400 if name is not given" do - post api("/projects", user) + it 'should respond with 400 if name is not given' do + post api('/projects', user) response.status.should == 400 end - it "should return a 403 error if project limit reached" do + it 'should return a 403 error if project limit reached' do (1..user.projects_limit).each do |p| - post api("/projects", user), name: "foo#{p}" + post api('/projects', user), name: "foo#{p}" end - post api("/projects", user), name: 'bar' + post api('/projects', user), name: 'bar' response.status.should == 403 end - it "should assign attributes to project" do + it 'should assign attributes to project' do project = attributes_for(:project, { path: 'camelCasePath', description: Faker::Lorem.sentence, @@ -153,69 +178,69 @@ describe API::API, api: true do wiki_enabled: false }) - post api("/projects", user), project + post api('/projects', user), project project.each_pair do |k,v| json_response[k.to_s].should == v end end - it "should set a project as public" do + it 'should set a project as public' do project = attributes_for(:project, :public) - post api("/projects", user), project + post api('/projects', user), project json_response['public'].should be_true json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC end - it "should set a project as public using :public" do + it 'should set a project as public using :public' do project = attributes_for(:project, { public: true }) - post api("/projects", user), project + post api('/projects', user), project json_response['public'].should be_true json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC end - it "should set a project as internal" do + it 'should set a project as internal' do project = attributes_for(:project, :internal) - post api("/projects", user), project + post api('/projects', user), project json_response['public'].should be_false json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL end - it "should set a project as internal overriding :public" do + it 'should set a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) - post api("/projects", user), project + post api('/projects', user), project json_response['public'].should be_false json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL end - it "should set a project as private" do + it 'should set a project as private' do project = attributes_for(:project, :private) - post api("/projects", user), project + post api('/projects', user), project json_response['public'].should be_false json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE end - it "should set a project as private using :public" do + it 'should set a project as private using :public' do project = attributes_for(:project, { public: false }) - post api("/projects", user), project + post api('/projects', user), project json_response['public'].should be_false json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE end end - describe "POST /projects/user/:id" do + describe 'POST /projects/user/:id' do before { project } before { admin } - it "should create new project without path" do + it 'should create new project without path' do expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) end - it "should not create new project without name" do + it 'should not create new project without name' do expect { post api("/projects/user/#{user.id}", admin) }.to_not change {Project.count} end - it "should respond with 201 on success" do + it 'should respond with 201 on success' do post api("/projects/user/#{user.id}", admin), name: 'foo' response.status.should == 201 end @@ -235,7 +260,7 @@ describe API::API, api: true do ] end - it "should assign attributes to project" do + it 'should assign attributes to project' do project = attributes_for(:project, { description: Faker::Lorem.sentence, issues_enabled: false, @@ -251,42 +276,42 @@ describe API::API, api: true do end end - it "should set a project as public" do + it 'should set a project as public' do project = attributes_for(:project, :public) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_true json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC end - it "should set a project as public using :public" do + it 'should set a project as public using :public' do project = attributes_for(:project, { public: true }) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_true json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC end - it "should set a project as internal" do + it 'should set a project as internal' do project = attributes_for(:project, :internal) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_false json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL end - it "should set a project as internal overriding :public" do + it 'should set a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_false json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL end - it "should set a project as private" do + it 'should set a project as private' do project = attributes_for(:project, :private) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_false json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE end - it "should set a project as private using :public" do + it 'should set a project as private using :public' do project = attributes_for(:project, { public: false }) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_false @@ -294,30 +319,30 @@ describe API::API, api: true do end end - describe "GET /projects/:id" do + describe 'GET /projects/:id' do before { project } before { project_member } - it "should return a project by id" do + it 'should return a project by id' do get api("/projects/#{project.id}", user) response.status.should == 200 json_response['name'].should == project.name json_response['owner']['username'].should == user.username end - it "should return a project by path name" do + it 'should return a project by path name' do get api("/projects/#{project.id}", user) response.status.should == 200 json_response['name'].should == project.name end - it "should return a 404 error if not found" do - get api("/projects/42", user) + it 'should return a 404 error if not found' do + get api('/projects/42', user) response.status.should == 404 json_response['message'].should == '404 Project Not Found' end - it "should return a 404 error if user is not a member" do + it 'should return a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}", other_user) response.status.should == 404 @@ -331,8 +356,8 @@ describe API::API, api: true do end it { response.status.should == 200 } - it { json_response['permissions']["project_access"]["access_level"].should == Gitlab::Access::MASTER } - it { json_response['permissions']["group_access"].should be_nil } + it { json_response['permissions']['project_access']['access_level'].should == Gitlab::Access::MASTER } + it { json_response['permissions']['group_access'].should be_nil } end context 'group project' do @@ -343,16 +368,16 @@ describe API::API, api: true do end it { response.status.should == 200 } - it { json_response['permissions']["project_access"].should be_nil } - it { json_response['permissions']["group_access"]["access_level"].should == Gitlab::Access::OWNER } + it { json_response['permissions']['project_access'].should be_nil } + it { json_response['permissions']['group_access']['access_level'].should == Gitlab::Access::OWNER } end end end - describe "GET /projects/:id/events" do + describe 'GET /projects/:id/events' do before { project_member } - it "should return a project events" do + it 'should return a project events' do get api("/projects/#{project.id}/events", user) response.status.should == 200 json_event = json_response.first @@ -362,23 +387,23 @@ describe API::API, api: true do json_event['author_username'].should == user.username end - it "should return a 404 error if not found" do - get api("/projects/42/events", user) + it 'should return a 404 error if not found' do + get api('/projects/42/events', user) response.status.should == 404 json_response['message'].should == '404 Project Not Found' end - it "should return a 404 error if user is not a member" do + it 'should return a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}/events", other_user) response.status.should == 404 end end - describe "GET /projects/:id/snippets" do + describe 'GET /projects/:id/snippets' do before { snippet } - it "should return an array of project snippets" do + it 'should return an array of project snippets' do get api("/projects/#{project.id}/snippets", user) response.status.should == 200 json_response.should be_an Array @@ -386,48 +411,48 @@ describe API::API, api: true do end end - describe "GET /projects/:id/snippets/:snippet_id" do - it "should return a project snippet" do + describe 'GET /projects/:id/snippets/:snippet_id' do + it 'should return a project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}", user) response.status.should == 200 json_response['title'].should == snippet.title end - it "should return a 404 error if snippet id not found" do + it 'should return a 404 error if snippet id not found' do get api("/projects/#{project.id}/snippets/1234", user) response.status.should == 404 end end - describe "POST /projects/:id/snippets" do - it "should create a new project snippet" do + describe 'POST /projects/:id/snippets' do + it 'should create a new project snippet' do post api("/projects/#{project.id}/snippets", user), title: 'api test', file_name: 'sample.rb', code: 'test' response.status.should == 201 json_response['title'].should == 'api test' end - it "should return a 400 error if title is not given" do + it 'should return a 400 error if title is not given' do post api("/projects/#{project.id}/snippets", user), file_name: 'sample.rb', code: 'test' response.status.should == 400 end - it "should return a 400 error if file_name not given" do + it 'should return a 400 error if file_name not given' do post api("/projects/#{project.id}/snippets", user), title: 'api test', code: 'test' response.status.should == 400 end - it "should return a 400 error if code not given" do + it 'should return a 400 error if code not given' do post api("/projects/#{project.id}/snippets", user), title: 'api test', file_name: 'sample.rb' response.status.should == 400 end end - describe "PUT /projects/:id/snippets/:shippet_id" do - it "should update an existing project snippet" do + describe 'PUT /projects/:id/snippets/:shippet_id' do + it 'should update an existing project snippet' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), code: 'updated code' response.status.should == 200 @@ -435,7 +460,7 @@ describe API::API, api: true do snippet.reload.content.should == 'updated code' end - it "should update an existing project snippet with new title" do + it 'should update an existing project snippet with new title' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), title: 'other api test' response.status.should == 200 @@ -443,10 +468,10 @@ describe API::API, api: true do end end - describe "DELETE /projects/:id/snippets/:snippet_id" do + describe 'DELETE /projects/:id/snippets/:snippet_id' do before { snippet } - it "should delete existing project snippet" do + it 'should delete existing project snippet' do expect { delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) }.to change { Snippet.count }.by(-1) @@ -459,13 +484,13 @@ describe API::API, api: true do end end - describe "GET /projects/:id/snippets/:snippet_id/raw" do - it "should get a raw project snippet" do + describe 'GET /projects/:id/snippets/:snippet_id/raw' do + it 'should get a raw project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) response.status.should == 200 end - it "should return a 404 error if raw project snippet not found" do + it 'should return a 404 error if raw project snippet not found' do get api("/projects/#{project.id}/snippets/5555/raw", user) response.status.should == 404 end @@ -475,10 +500,10 @@ describe API::API, api: true do let(:deploy_keys_project) { create(:deploy_keys_project, project: project) } let(:deploy_key) { deploy_keys_project.deploy_key } - describe "GET /projects/:id/keys" do + describe 'GET /projects/:id/keys' do before { deploy_key } - it "should return array of ssh keys" do + it 'should return array of ssh keys' do get api("/projects/#{project.id}/keys", user) response.status.should == 200 json_response.should be_an Array @@ -486,22 +511,22 @@ describe API::API, api: true do end end - describe "GET /projects/:id/keys/:key_id" do - it "should return a single key" do + describe 'GET /projects/:id/keys/:key_id' do + it 'should return a single key' do get api("/projects/#{project.id}/keys/#{deploy_key.id}", user) response.status.should == 200 json_response['title'].should == deploy_key.title end - it "should return 404 Not Found with invalid ID" do + it 'should return 404 Not Found with invalid ID' do get api("/projects/#{project.id}/keys/404", user) response.status.should == 404 end end - describe "POST /projects/:id/keys" do - it "should not create an invalid ssh key" do - post api("/projects/#{project.id}/keys", user), { title: "invalid key" } + describe 'POST /projects/:id/keys' do + it 'should not create an invalid ssh key' do + post api("/projects/#{project.id}/keys", user), { title: 'invalid key' } response.status.should == 400 json_response['message']['key'].should == [ 'can\'t be blank', @@ -519,7 +544,7 @@ describe API::API, api: true do ] end - it "should create new ssh key" do + it 'should create new ssh key' do key_attrs = attributes_for :key expect { post api("/projects/#{project.id}/keys", user), key_attrs @@ -527,16 +552,16 @@ describe API::API, api: true do end end - describe "DELETE /projects/:id/keys/:key_id" do + describe 'DELETE /projects/:id/keys/:key_id' do before { deploy_key } - it "should delete existing key" do + it 'should delete existing key' do expect { delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user) }.to change{ project.deploy_keys.count }.by(-1) end - it "should return 404 Not Found with invalid ID" do + it 'should return 404 Not Found with invalid ID' do delete api("/projects/#{project.id}/keys/404", user) response.status.should == 404 end @@ -547,7 +572,7 @@ describe API::API, api: true do let(:project_fork_target) { create(:project) } let(:project_fork_source) { create(:project, :public) } - describe "POST /projects/:id/fork/:forked_from_id" do + describe 'POST /projects/:id/fork/:forked_from_id' do let(:new_project_fork_source) { create(:project, :public) } it "shouldn't available for non admin users" do @@ -555,7 +580,7 @@ describe API::API, api: true do response.status.should == 403 end - it "should allow project to be forked from an existing project" do + it 'should allow project to be forked from an existing project' do project_fork_target.forked?.should_not be_true post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) response.status.should == 201 @@ -565,12 +590,12 @@ describe API::API, api: true do project_fork_target.forked?.should be_true end - it "should fail if forked_from project which does not exist" do + it 'should fail if forked_from project which does not exist' do post api("/projects/#{project_fork_target.id}/fork/9999", admin) response.status.should == 404 end - it "should fail with 409 if already forked" do + it 'should fail with 409 if already forked' do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) project_fork_target.reload project_fork_target.forked_from_project.id.should == project_fork_source.id @@ -582,14 +607,14 @@ describe API::API, api: true do end end - describe "DELETE /projects/:id/fork" do + describe 'DELETE /projects/:id/fork' do it "shouldn't available for non admin users" do delete api("/projects/#{project_fork_target.id}/fork", user) response.status.should == 403 end - it "should make forked project unforked" do + it 'should make forked project unforked' do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) project_fork_target.reload project_fork_target.forked_from_project.should_not be_nil @@ -601,7 +626,7 @@ describe API::API, api: true do project_fork_target.forked?.should_not be_true end - it "should be idempotent if not forked" do + it 'should be idempotent if not forked' do project_fork_target.forked_from_project.should be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) response.status.should == 200 @@ -610,7 +635,7 @@ describe API::API, api: true do end end - describe "GET /projects/search/:query" do + describe 'GET /projects/search/:query' do let!(:query) { 'query'} let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) } let!(:pre) { create(:empty_project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } @@ -622,15 +647,15 @@ describe API::API, api: true do let!(:public) { create(:empty_project, :public, name: "public #{query}") } let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') } - context "when unauthenticated" do - it "should return authentication error" do + context 'when unauthenticated' do + it 'should return authentication error' do get api("/projects/search/#{query}") response.status.should == 401 end end - context "when authenticated" do - it "should return an array of projects" do + context 'when authenticated' do + it 'should return an array of projects' do get api("/projects/search/#{query}",user) response.status.should == 200 json_response.should be_an Array @@ -639,8 +664,8 @@ describe API::API, api: true do end end - context "when authenticated as a different user" do - it "should return matching public projects" do + context 'when authenticated as a different user' do + it 'should return matching public projects' do get api("/projects/search/#{query}", user2) response.status.should == 200 json_response.should be_an Array @@ -650,9 +675,121 @@ describe API::API, api: true do end end - describe "DELETE /projects/:id" do - context "when authenticated as user" do - it "should remove project" do + describe 'PUT /projects/:id̈́' do + before { project } + before { user } + before { user3 } + before { user4 } + before { project3 } + before { project4 } + before { project_member3 } + before { project_member2 } + + context 'when unauthenticated' do + it 'should return authentication error' do + project_param = { name: 'bar' } + put api("/projects/#{project.id}"), project_param + response.status.should == 401 + end + end + + context 'when authenticated as project owner' do + it 'should update name' do + project_param = { name: 'bar' } + put api("/projects/#{project.id}", user), project_param + response.status.should == 200 + project_param.each_pair do |k, v| + json_response[k.to_s].should == v + end + end + + it 'should update visibility_level' do + project_param = { visibility_level: 20 } + put api("/projects/#{project3.id}", user), project_param + response.status.should == 200 + project_param.each_pair do |k, v| + json_response[k.to_s].should == v + end + end + + it 'should not update name to existing name' do + project_param = { name: project3.name } + put api("/projects/#{project.id}", user), project_param + response.status.should == 400 + json_response['message']['name'].should == ['has already been taken'] + end + + it 'should update path & name to existing path & name in different namespace' do + project_param = { path: project4.path, name: project4.name } + put api("/projects/#{project3.id}", user), project_param + response.status.should == 200 + project_param.each_pair do |k, v| + json_response[k.to_s].should == v + end + end + end + + context 'when authenticated as project master' do + it 'should update path' do + project_param = { path: 'bar' } + put api("/projects/#{project3.id}", user4), project_param + response.status.should == 200 + project_param.each_pair do |k, v| + json_response[k.to_s].should == v + end + end + + it 'should update other attributes' do + project_param = { issues_enabled: true, + wiki_enabled: true, + snippets_enabled: true, + merge_requests_enabled: true, + description: 'new description' } + + put api("/projects/#{project3.id}", user4), project_param + response.status.should == 200 + project_param.each_pair do |k, v| + json_response[k.to_s].should == v + end + end + + it 'should not update path to existing path' do + project_param = { path: project.path } + put api("/projects/#{project3.id}", user4), project_param + response.status.should == 400 + json_response['message']['path'].should == ['has already been taken'] + end + + it 'should not update name' do + project_param = { name: 'bar' } + put api("/projects/#{project3.id}", user4), project_param + response.status.should == 403 + end + + it 'should not update visibility_level' do + project_param = { visibility_level: 20 } + put api("/projects/#{project3.id}", user4), project_param + response.status.should == 403 + end + end + + context 'when authenticated as project developer' do + it 'should not update other attributes' do + project_param = { path: 'bar', + issues_enabled: true, + wiki_enabled: true, + snippets_enabled: true, + merge_requests_enabled: true, + description: 'new description' } + put api("/projects/#{project.id}", user3), project_param + response.status.should == 403 + end + end + end + + describe 'DELETE /projects/:id' do + context 'when authenticated as user' do + it 'should remove project' do expect(GitlabShellWorker).to( receive(:perform_async).with(:remove_repository, /#{project.path_with_namespace}/) @@ -662,32 +799,32 @@ describe API::API, api: true do response.status.should == 200 end - it "should not remove a project if not an owner" do + it 'should not remove a project if not an owner' do user3 = create(:user) project.team << [user3, :developer] delete api("/projects/#{project.id}", user3) response.status.should == 403 end - it "should not remove a non existing project" do - delete api("/projects/1328", user) + it 'should not remove a non existing project' do + delete api('/projects/1328', user) response.status.should == 404 end - it "should not remove a project not attached to user" do + it 'should not remove a project not attached to user' do delete api("/projects/#{project.id}", user2) response.status.should == 404 end end - context "when authenticated as admin" do - it "should remove any existing project" do + context 'when authenticated as admin' do + it 'should remove any existing project' do delete api("/projects/#{project.id}", admin) response.status.should == 200 end - it "should not remove a non existing project" do - delete api("/projects/1328", admin) + it 'should not remove a non existing project' do + delete api('/projects/1328', admin) response.status.should == 404 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 f149f3f62a9..e36b266a1ff 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -12,43 +12,43 @@ require 'spec_helper' # Examples # # # Default behavior -# it_behaves_like "RESTful project resources" do +# it_behaves_like 'RESTful project resources' do # let(:controller) { 'issues' } # end # # # Customizing actions -# it_behaves_like "RESTful project resources" do +# it_behaves_like 'RESTful project resources' do # let(:actions) { [:index] } # let(:controller) { 'issues' } # end -shared_examples "RESTful project resources" do +shared_examples 'RESTful project resources' do let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } - it "to #index" do + it 'to #index' do get("/gitlab/gitlabhq/#{controller}").should route_to("projects/#{controller}#index", project_id: 'gitlab/gitlabhq') if actions.include?(:index) end - it "to #create" do + it 'to #create' do post("/gitlab/gitlabhq/#{controller}").should route_to("projects/#{controller}#create", project_id: 'gitlab/gitlabhq') if actions.include?(:create) end - it "to #new" do + it 'to #new' do get("/gitlab/gitlabhq/#{controller}/new").should route_to("projects/#{controller}#new", project_id: 'gitlab/gitlabhq') if actions.include?(:new) end - it "to #edit" do + it 'to #edit' do get("/gitlab/gitlabhq/#{controller}/1/edit").should route_to("projects/#{controller}#edit", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:edit) end - it "to #show" do + it 'to #show' do get("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#show", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:show) end - it "to #update" do + it 'to #update' do put("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#update", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:update) end - it "to #destroy" do + it 'to #destroy' do delete("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#destroy", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:destroy) end end @@ -61,33 +61,33 @@ end # PUT /:id(.:format) projects#update # DELETE /:id(.:format) projects#destroy # markdown_preview_project GET /:id/markdown_preview(.:format) projects#markdown_preview -describe ProjectsController, "routing" do - it "to #create" do - post("/projects").should route_to('projects#create') +describe ProjectsController, 'routing' do + it 'to #create' do + post('/projects').should route_to('projects#create') end - it "to #new" do - get("/projects/new").should route_to('projects#new') + it 'to #new' do + get('/projects/new').should route_to('projects#new') end - it "to #edit" do - get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq') + it 'to #edit' do + get('/gitlab/gitlabhq/edit').should route_to('projects#edit', id: 'gitlab/gitlabhq') end - it "to #autocomplete_sources" do - get('/gitlab/gitlabhq/autocomplete_sources').should route_to('projects#autocomplete_sources', id: "gitlab/gitlabhq") + it 'to #autocomplete_sources' do + get('/gitlab/gitlabhq/autocomplete_sources').should route_to('projects#autocomplete_sources', id: 'gitlab/gitlabhq') end - it "to #show" do - get("/gitlab/gitlabhq").should route_to('projects#show', id: 'gitlab/gitlabhq') + it 'to #show' do + get('/gitlab/gitlabhq').should route_to('projects#show', id: 'gitlab/gitlabhq') end - it "to #update" do - put("/gitlab/gitlabhq").should route_to('projects#update', id: 'gitlab/gitlabhq') + it 'to #update' do + put('/gitlab/gitlabhq').should route_to('projects#update', id: 'gitlab/gitlabhq') end - it "to #destroy" do - delete("/gitlab/gitlabhq").should route_to('projects#destroy', id: 'gitlab/gitlabhq') + it 'to #destroy' do + delete('/gitlab/gitlabhq').should route_to('projects#destroy', id: 'gitlab/gitlabhq') end it 'to #markdown_preview' do @@ -103,16 +103,16 @@ end # edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit # project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show # DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy -describe Projects::WikisController, "routing" do - it "to #pages" do - get("/gitlab/gitlabhq/wikis/pages").should route_to('projects/wikis#pages', project_id: 'gitlab/gitlabhq') +describe Projects::WikisController, 'routing' do + it 'to #pages' do + get('/gitlab/gitlabhq/wikis/pages').should route_to('projects/wikis#pages', project_id: 'gitlab/gitlabhq') end - it "to #history" do - get("/gitlab/gitlabhq/wikis/1/history").should route_to('projects/wikis#history', project_id: 'gitlab/gitlabhq', id: '1') + it 'to #history' do + get('/gitlab/gitlabhq/wikis/1/history').should route_to('projects/wikis#history', project_id: 'gitlab/gitlabhq', id: '1') end - it_behaves_like "RESTful project resources" do + it_behaves_like 'RESTful project resources' do let(:actions) { [:create, :edit, :show, :destroy] } let(:controller) { 'wikis' } end @@ -122,45 +122,45 @@ end # tags_project_repository GET /:project_id/repository/tags(.:format) projects/repositories#tags # archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive # edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit -describe Projects::RepositoriesController, "routing" do - it "to #archive" do - get("/gitlab/gitlabhq/repository/archive").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq') +describe Projects::RepositoriesController, 'routing' do + it 'to #archive' do + get('/gitlab/gitlabhq/repository/archive').should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq') end - it "to #archive format:zip" do - get("/gitlab/gitlabhq/repository/archive.zip").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'zip') + it 'to #archive format:zip' do + get('/gitlab/gitlabhq/repository/archive.zip').should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'zip') end - it "to #archive format:tar.bz2" do - get("/gitlab/gitlabhq/repository/archive.tar.bz2").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'tar.bz2') + it 'to #archive format:tar.bz2' do + get('/gitlab/gitlabhq/repository/archive.tar.bz2').should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'tar.bz2') end - it "to #show" do - get("/gitlab/gitlabhq/repository").should route_to('projects/repositories#show', project_id: 'gitlab/gitlabhq') + it 'to #show' do + get('/gitlab/gitlabhq/repository').should route_to('projects/repositories#show', project_id: 'gitlab/gitlabhq') end end -describe Projects::BranchesController, "routing" do - it "to #branches" do - get("/gitlab/gitlabhq/branches").should route_to('projects/branches#index', project_id: 'gitlab/gitlabhq') - delete("/gitlab/gitlabhq/branches/feature%2345").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45') - delete("/gitlab/gitlabhq/branches/feature%2B45").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45') - delete("/gitlab/gitlabhq/branches/feature@45").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45') - delete("/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz') - delete("/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz') - delete("/gitlab/gitlabhq/branches/feature@45/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz') +describe Projects::BranchesController, 'routing' do + it 'to #branches' do + get('/gitlab/gitlabhq/branches').should route_to('projects/branches#index', project_id: 'gitlab/gitlabhq') + delete('/gitlab/gitlabhq/branches/feature%2345').should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45') + delete('/gitlab/gitlabhq/branches/feature%2B45').should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45') + delete('/gitlab/gitlabhq/branches/feature@45').should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45') + delete('/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz').should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz') + delete('/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz').should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz') + delete('/gitlab/gitlabhq/branches/feature@45/foo/bar/baz').should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz') end end -describe Projects::TagsController, "routing" do - it "to #tags" do - get("/gitlab/gitlabhq/tags").should route_to('projects/tags#index', project_id: 'gitlab/gitlabhq') - delete("/gitlab/gitlabhq/tags/feature%2345").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45') - delete("/gitlab/gitlabhq/tags/feature%2B45").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45') - delete("/gitlab/gitlabhq/tags/feature@45").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45') - delete("/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz') - delete("/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz') - delete("/gitlab/gitlabhq/tags/feature@45/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz') +describe Projects::TagsController, 'routing' do + it 'to #tags' do + get('/gitlab/gitlabhq/tags').should route_to('projects/tags#index', project_id: 'gitlab/gitlabhq') + delete('/gitlab/gitlabhq/tags/feature%2345').should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45') + delete('/gitlab/gitlabhq/tags/feature%2B45').should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45') + delete('/gitlab/gitlabhq/tags/feature@45').should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45') + delete('/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz').should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz') + delete('/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz').should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz') + delete('/gitlab/gitlabhq/tags/feature@45/foo/bar/baz').should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz') end end @@ -172,8 +172,8 @@ end # project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show # PUT /:project_id/deploy_keys/:id(.:format) deploy_keys#update # DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy -describe Projects::DeployKeysController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::DeployKeysController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:controller) { 'deploy_keys' } end end @@ -181,8 +181,8 @@ end # project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index # POST /:project_id/protected_branches(.:format) protected_branches#create # project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy -describe Projects::ProtectedBranchesController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::ProtectedBranchesController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :create, :destroy] } let(:controller) { 'protected_branches' } end @@ -191,21 +191,21 @@ end # switch_project_refs GET /:project_id/refs/switch(.:format) refs#switch # logs_tree_project_ref GET /:project_id/refs/:id/logs_tree(.:format) refs#logs_tree # logs_file_project_ref GET /:project_id/refs/:id/logs_tree/:path(.:format) refs#logs_tree -describe Projects::RefsController, "routing" do - it "to #switch" do - get("/gitlab/gitlabhq/refs/switch").should route_to('projects/refs#switch', project_id: 'gitlab/gitlabhq') - end - - it "to #logs_tree" do - get("/gitlab/gitlabhq/refs/stable/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable') - get("/gitlab/gitlabhq/refs/feature%2345/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45') - get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45') - get("/gitlab/gitlabhq/refs/feature@45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45') - get("/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'foo/bar/baz') - get("/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45', path: 'foo/bar/baz') - get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45', path: 'foo/bar/baz') - get("/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45', path: 'foo/bar/baz') - get("/gitlab/gitlabhq/refs/stable/logs_tree/files.scss").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss') +describe Projects::RefsController, 'routing' do + it 'to #switch' do + get('/gitlab/gitlabhq/refs/switch').should route_to('projects/refs#switch', project_id: 'gitlab/gitlabhq') + end + + it 'to #logs_tree' do + get('/gitlab/gitlabhq/refs/stable/logs_tree').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable') + get('/gitlab/gitlabhq/refs/feature%2345/logs_tree').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45') + get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45') + get('/gitlab/gitlabhq/refs/feature@45/logs_tree').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45') + get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'foo/bar/baz') + get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45', path: 'foo/bar/baz') + get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45', path: 'foo/bar/baz') + get('/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45', path: 'foo/bar/baz') + get('/gitlab/gitlabhq/refs/stable/logs_tree/files.scss').should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss') end end @@ -221,36 +221,36 @@ end # project_merge_request GET /:project_id/merge_requests/:id(.:format) projects/merge_requests#show # PUT /:project_id/merge_requests/:id(.:format) projects/merge_requests#update # DELETE /:project_id/merge_requests/:id(.:format) projects/merge_requests#destroy -describe Projects::MergeRequestsController, "routing" do - it "to #diffs" do - get("/gitlab/gitlabhq/merge_requests/1/diffs").should route_to('projects/merge_requests#diffs', project_id: 'gitlab/gitlabhq', id: '1') +describe Projects::MergeRequestsController, 'routing' do + it 'to #diffs' do + get('/gitlab/gitlabhq/merge_requests/1/diffs').should route_to('projects/merge_requests#diffs', project_id: 'gitlab/gitlabhq', id: '1') end - it "to #automerge" do + it 'to #automerge' do post('/gitlab/gitlabhq/merge_requests/1/automerge').should route_to( 'projects/merge_requests#automerge', project_id: 'gitlab/gitlabhq', id: '1' ) end - it "to #automerge_check" do - get("/gitlab/gitlabhq/merge_requests/1/automerge_check").should route_to('projects/merge_requests#automerge_check', project_id: 'gitlab/gitlabhq', id: '1') + it 'to #automerge_check' do + get('/gitlab/gitlabhq/merge_requests/1/automerge_check').should route_to('projects/merge_requests#automerge_check', project_id: 'gitlab/gitlabhq', id: '1') end - it "to #branch_from" do - get("/gitlab/gitlabhq/merge_requests/branch_from").should route_to('projects/merge_requests#branch_from', project_id: 'gitlab/gitlabhq') + it 'to #branch_from' do + get('/gitlab/gitlabhq/merge_requests/branch_from').should route_to('projects/merge_requests#branch_from', project_id: 'gitlab/gitlabhq') end - it "to #branch_to" do - get("/gitlab/gitlabhq/merge_requests/branch_to").should route_to('projects/merge_requests#branch_to', project_id: 'gitlab/gitlabhq') + it 'to #branch_to' do + get('/gitlab/gitlabhq/merge_requests/branch_to').should route_to('projects/merge_requests#branch_to', project_id: 'gitlab/gitlabhq') end - it "to #show" do - get("/gitlab/gitlabhq/merge_requests/1.diff").should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'diff') - get("/gitlab/gitlabhq/merge_requests/1.patch").should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'patch') + it 'to #show' do + get('/gitlab/gitlabhq/merge_requests/1.diff').should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'diff') + get('/gitlab/gitlabhq/merge_requests/1.patch').should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'patch') end - it_behaves_like "RESTful project resources" do + it_behaves_like 'RESTful project resources' do let(:controller) { 'merge_requests' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } end @@ -264,37 +264,37 @@ end # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show # PUT /:project_id/snippets/:id(.:format) snippets#update # DELETE /:project_id/snippets/:id(.:format) snippets#destroy -describe SnippetsController, "routing" do - it "to #raw" do - get("/gitlab/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlab/gitlabhq', id: '1') +describe SnippetsController, 'routing' do + it 'to #raw' do + get('/gitlab/gitlabhq/snippets/1/raw').should route_to('projects/snippets#raw', project_id: 'gitlab/gitlabhq', id: '1') end - it "to #index" do - get("/gitlab/gitlabhq/snippets").should route_to("projects/snippets#index", project_id: 'gitlab/gitlabhq') + it 'to #index' do + get('/gitlab/gitlabhq/snippets').should route_to('projects/snippets#index', project_id: 'gitlab/gitlabhq') end - it "to #create" do - post("/gitlab/gitlabhq/snippets").should route_to("projects/snippets#create", project_id: 'gitlab/gitlabhq') + it 'to #create' do + post('/gitlab/gitlabhq/snippets').should route_to('projects/snippets#create', project_id: 'gitlab/gitlabhq') end - it "to #new" do - get("/gitlab/gitlabhq/snippets/new").should route_to("projects/snippets#new", project_id: 'gitlab/gitlabhq') + it 'to #new' do + get('/gitlab/gitlabhq/snippets/new').should route_to('projects/snippets#new', project_id: 'gitlab/gitlabhq') end - it "to #edit" do - get("/gitlab/gitlabhq/snippets/1/edit").should route_to("projects/snippets#edit", project_id: 'gitlab/gitlabhq', id: '1') + it 'to #edit' do + get('/gitlab/gitlabhq/snippets/1/edit').should route_to('projects/snippets#edit', project_id: 'gitlab/gitlabhq', id: '1') end - it "to #show" do - get("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#show", project_id: 'gitlab/gitlabhq', id: '1') + it 'to #show' do + get('/gitlab/gitlabhq/snippets/1').should route_to('projects/snippets#show', project_id: 'gitlab/gitlabhq', id: '1') end - it "to #update" do - put("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#update", project_id: 'gitlab/gitlabhq', id: '1') + it 'to #update' do + put('/gitlab/gitlabhq/snippets/1').should route_to('projects/snippets#update', project_id: 'gitlab/gitlabhq', id: '1') end - it "to #destroy" do - delete("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#destroy", project_id: 'gitlab/gitlabhq', id: '1') + it 'to #destroy' do + delete('/gitlab/gitlabhq/snippets/1').should route_to('projects/snippets#destroy', project_id: 'gitlab/gitlabhq', id: '1') end end @@ -302,24 +302,24 @@ end # project_hooks GET /:project_id/hooks(.:format) hooks#index # POST /:project_id/hooks(.:format) hooks#create # project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy -describe Projects::HooksController, "routing" do - it "to #test" do - get("/gitlab/gitlabhq/hooks/1/test").should route_to('projects/hooks#test', project_id: 'gitlab/gitlabhq', id: '1') +describe Projects::HooksController, 'routing' do + it 'to #test' do + get('/gitlab/gitlabhq/hooks/1/test').should route_to('projects/hooks#test', project_id: 'gitlab/gitlabhq', id: '1') end - it_behaves_like "RESTful project resources" do + it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :create, :destroy] } let(:controller) { 'hooks' } end end # project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/} -describe Projects::CommitController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/commit/4246fb").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb') - get("/gitlab/gitlabhq/commit/4246fb.diff").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'diff') - get("/gitlab/gitlabhq/commit/4246fb.patch").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'patch') - get("/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') +describe Projects::CommitController, 'routing' do + it 'to #show' do + get('/gitlab/gitlabhq/commit/4246fb').should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb') + get('/gitlab/gitlabhq/commit/4246fb.diff').should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'diff') + get('/gitlab/gitlabhq/commit/4246fb.patch').should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'patch') + get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5').should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') end end @@ -327,14 +327,14 @@ end # project_commits GET /:project_id/commits(.:format) commits#index # POST /:project_id/commits(.:format) commits#create # project_commit GET /:project_id/commits/:id(.:format) commits#show -describe Projects::CommitsController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::CommitsController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:actions) { [:show] } let(:controller) { 'commits' } end - it "to #show" do - get("/gitlab/gitlabhq/commits/master.atom").should route_to('projects/commits#show', project_id: 'gitlab/gitlabhq', id: "master", format: "atom") + it 'to #show' do + get('/gitlab/gitlabhq/commits/master.atom').should route_to('projects/commits#show', project_id: 'gitlab/gitlabhq', id: 'master', format: 'atom') end end @@ -345,8 +345,8 @@ end # project_team_member GET /:project_id/team_members/:id(.:format) team_members#show # PUT /:project_id/team_members/:id(.:format) team_members#update # DELETE /:project_id/team_members/:id(.:format) team_members#destroy -describe Projects::TeamMembersController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::TeamMembersController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:actions) { [:new, :create, :update, :destroy] } let(:controller) { 'team_members' } end @@ -359,17 +359,17 @@ end # project_milestone GET /:project_id/milestones/:id(.:format) milestones#show # PUT /:project_id/milestones/:id(.:format) milestones#update # DELETE /:project_id/milestones/:id(.:format) milestones#destroy -describe Projects::MilestonesController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::MilestonesController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:controller) { 'milestones' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } end end # project_labels GET /:project_id/labels(.:format) labels#index -describe Projects::LabelsController, "routing" do - it "to #index" do - get("/gitlab/gitlabhq/labels").should route_to('projects/labels#index', project_id: 'gitlab/gitlabhq') +describe Projects::LabelsController, 'routing' do + it 'to #index' do + get('/gitlab/gitlabhq/labels').should route_to('projects/labels#index', project_id: 'gitlab/gitlabhq') end end @@ -383,12 +383,12 @@ end # project_issue GET /:project_id/issues/:id(.:format) issues#show # PUT /:project_id/issues/:id(.:format) issues#update # DELETE /:project_id/issues/:id(.:format) issues#destroy -describe Projects::IssuesController, "routing" do - it "to #bulk_update" do - post("/gitlab/gitlabhq/issues/bulk_update").should route_to('projects/issues#bulk_update', project_id: 'gitlab/gitlabhq') +describe Projects::IssuesController, 'routing' do + it 'to #bulk_update' do + post('/gitlab/gitlabhq/issues/bulk_update').should route_to('projects/issues#bulk_update', project_id: 'gitlab/gitlabhq') end - it_behaves_like "RESTful project resources" do + it_behaves_like 'RESTful project resources' do let(:controller) { 'issues' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } end @@ -397,54 +397,50 @@ end # project_notes GET /:project_id/notes(.:format) notes#index # POST /:project_id/notes(.:format) notes#create # project_note DELETE /:project_id/notes/:id(.:format) notes#destroy -describe Projects::NotesController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::NotesController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :create, :destroy] } let(:controller) { 'notes' } end end # project_blame GET /:project_id/blame/:id(.:format) blame#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::BlameController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/blame/master/app/models/project.rb").should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') - get("/gitlab/gitlabhq/blame/master/files.scss").should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') +describe Projects::BlameController, 'routing' do + it 'to #show' do + get('/gitlab/gitlabhq/blame/master/app/models/project.rb').should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') + get('/gitlab/gitlabhq/blame/master/files.scss').should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') end end # project_blob GET /:project_id/blob/:id(.:format) blob#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::BlobController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/blob/master/app/models/project.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') - get("/gitlab/gitlabhq/blob/master/app/models/compare.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/compare.rb') - get("/gitlab/gitlabhq/blob/master/app/models/diff.js").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/diff.js') - get("/gitlab/gitlabhq/blob/master/files.scss").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') +describe Projects::BlobController, 'routing' do + it 'to #show' do + get('/gitlab/gitlabhq/blob/master/app/models/project.rb').should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') + get('/gitlab/gitlabhq/blob/master/app/models/compare.rb').should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/compare.rb') + get('/gitlab/gitlabhq/blob/master/app/models/diff.js').should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/diff.js') + get('/gitlab/gitlabhq/blob/master/files.scss').should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') end end # project_tree GET /:project_id/tree/:id(.:format) tree#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::TreeController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/tree/master/app/models/project.rb").should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') - get("/gitlab/gitlabhq/tree/master/files.scss").should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') +describe Projects::TreeController, 'routing' do + it 'to #show' do + get('/gitlab/gitlabhq/tree/master/app/models/project.rb').should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') + get('/gitlab/gitlabhq/tree/master/files.scss').should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') 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 @@ -453,40 +449,48 @@ end # project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/} # POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/} # project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/} -describe Projects::CompareController, "routing" do - it "to #index" do - get("/gitlab/gitlabhq/compare").should route_to('projects/compare#index', project_id: 'gitlab/gitlabhq') +describe Projects::CompareController, 'routing' do + it 'to #index' do + get('/gitlab/gitlabhq/compare').should route_to('projects/compare#index', project_id: 'gitlab/gitlabhq') end - it "to #compare" do - post("/gitlab/gitlabhq/compare").should route_to('projects/compare#create', project_id: 'gitlab/gitlabhq') + it 'to #compare' do + post('/gitlab/gitlabhq/compare').should route_to('projects/compare#create', project_id: 'gitlab/gitlabhq') end - it "to #show" do - get("/gitlab/gitlabhq/compare/master...stable").should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'master', to: 'stable') - get("/gitlab/gitlabhq/compare/issue/1234...stable").should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'issue/1234', to: 'stable') + it 'to #show' do + get('/gitlab/gitlabhq/compare/master...stable').should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'master', to: 'stable') + get('/gitlab/gitlabhq/compare/issue/1234...stable').should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'issue/1234', to: 'stable') end end -describe Projects::NetworkController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/network/master").should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master') - get("/gitlab/gitlabhq/network/master.json").should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master', format: "json") +describe Projects::NetworkController, 'routing' do + it 'to #show' do + get('/gitlab/gitlabhq/network/master').should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master') + get('/gitlab/gitlabhq/network/master.json').should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master', format: 'json') end end -describe Projects::GraphsController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master') +describe Projects::GraphsController, 'routing' do + it 'to #show' do + get('/gitlab/gitlabhq/graphs/master').should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master') end end -describe Projects::ForksController, "routing" do - it "to #new" do - get("/gitlab/gitlabhq/fork/new").should route_to("projects/forks#new", project_id: 'gitlab/gitlabhq') +describe Projects::ForksController, 'routing' do + it 'to #new' do + get('/gitlab/gitlabhq/fork/new').should route_to('projects/forks#new', project_id: 'gitlab/gitlabhq') + end + + it 'to #create' do + post('/gitlab/gitlabhq/fork').should route_to('projects/forks#create', project_id: 'gitlab/gitlabhq') end +end - it "to #create" do - post("/gitlab/gitlabhq/fork").should route_to("projects/forks#create", project_id: 'gitlab/gitlabhq') +# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy +describe Projects::AvatarsController, 'routing' do + it 'to #destroy' do + delete('/gitlab/gitlabhq/avatar').should route_to( + 'projects/avatars#destroy', project_id: 'gitlab/gitlabhq') end 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) |