diff options
335 files changed, 4845 insertions, 2131 deletions
diff --git a/CHANGELOG b/CHANGELOG index de13ef3da55..97376c85ece 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,14 @@ Please view this file on the master branch, on stable branches it's out of date. v 7.9.0 (unreleased) + - Fix broken email images (Hannes Rosenögger) + - Fix mass SQL statements on initial push (Hannes Rosenögger) + - Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu) + - Add comment notification events to HipChat and Slack services (Stan Hu) + - Add issue and merge request events to HipChat and Slack services (Stan Hu) - Fix merge request URL passed to Webhooks. (Stan Hu) - Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu) + - Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu) - Move labels/milestones tabs to sidebar - Upgrade Rails gem to version 4.1.9. - Improve error messages for file edit failures @@ -13,6 +19,7 @@ v 7.9.0 (unreleased) - Fix ordering of imported but unchanged projects (Marco Wessel) - Mobile UI improvements: make aside content expandable - Expose avatar_url in projects API + - Fix checkbox alignment on the application settings page. - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) - Fix mass-unassignment of issues (Robert Speicher) - Allow user confirmation to be skipped for new users via API @@ -24,6 +31,43 @@ v 7.9.0 (unreleased) - Add Bitbucket omniauth provider. - Add Bitbucket importer. - Support referencing issues to a project whose name starts with a digit + - Condense commits already in target branch when updating merge request source branch. + - Send notifications and leave system comments when bulk updating issues. + - Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison) + - Move groups page from profile to dashboard + - Starred projects page at dashboard + - Blocking user does not remove him/her from project/groups but show blocked label + - Change subject of EmailsOnPush emails to include namespace, project and branch. + - Change subject of EmailsOnPush emails to include first commit message when multiple were pushed. + - Remove confusing footer from EmailsOnPush mail body. + - Add list of changed files to EmailsOnPush emails. + - Add option to send EmailsOnPush emails from committer email if domain matches. + - Add option to disable code diffs in EmailOnPush emails. + - Wrap commit message in EmailsOnPush email. + - Send EmailsOnPush emails when deleting commits using force push. + - Fix EmailsOnPush email comparison link to include first commit. + - Fix highliht of selected lines in file + - Reject access to group/project avatar if the user doesn't have access. + - Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update) + - Add GitLab active users count to rake gitlab:check + - Starred projects page at dashboard + - Make email display name configurable + - Improve json validation in hook data + - Use Emoji One + - Updated emoji help documentation to properly reference EmojiOne. + - Fix missing GitHub organisation repositories on import page. + - Added blue thmeme + - Remove annoying notice messages when create/update merge request + - Allow smb:// links in Markdown text. + - Filter merge request by title or description at Merge Requests page + - Block user if he/she was blocked in Active Directory + +v 7.8.4 + - Fix issue_tracker_id substitution in custom issue trackers + - Fix path and name duplication in namespaces + +v 7.8.3 + - Bump version of gitlab_git fixing annotated tags without message v 7.8.2 - Fix service migration issue when upgrading from versions prior to 7.3 @@ -32,7 +76,9 @@ v 7.8.2 - Fix response of push to repository to return "Not found" if user doesn't have access - Fix check if user is allowed to view the file attachment - Fix import check for case sensetive namespaces - - Added issue and merge request events to Slack service (Stan Hu) + - Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time. + - Properly handle autosave local storage exceptions. + - Escape wildcards when searching LDAP by username. v 7.8.1 - Fix run of custom post receive hooks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73a8f9eb49f..42b5ce22e32 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,6 +63,8 @@ Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint. +To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) and see [Development section](doc/development/README.md) in the help file. + ### Merge request guidelines If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: @@ -39,7 +39,7 @@ gem "browser" # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '7.0.0.rc15' +gem "gitlab_git", '7.1.0' # Ruby/Rack Git Smart-HTTP Server Handler gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack' @@ -88,7 +88,7 @@ gem "six" gem "seed-fu" # Markup pipeline for GitLab -gem 'html-pipeline-gitlab', '~> 0.1.0' +gem 'html-pipeline-gitlab', '~> 0.1' # Markdown to HTML gem "github-markup" @@ -177,6 +177,9 @@ gem 'ace-rails-ap' # Keyboard shortcuts gem 'mousetrap-rails' +# Shutting down requests that take too long +gem "slowpoke" + gem "sass-rails", '~> 4.0.2' gem "coffee-rails" gem "uglifier" @@ -191,7 +194,7 @@ gem "jquery-scrollto-rails" gem "raphael-rails", "~> 2.1.2" gem 'bootstrap-sass', '~> 3.0' gem "font-awesome-rails", '~> 4.2' -gem "gitlab_emoji", "~> 0.0.1.1" +gem "gitlab_emoji", "~> 0.1" gem "gon", '~> 5.0.0' gem 'nprogress-rails' gem 'request_store' @@ -250,7 +253,7 @@ group :development, :test do gem 'jasmine', '2.0.2' - gem "spring", '1.3.1' + gem "spring", '~> 1.3.1' gem "spring-commands-rspec", '1.0.4' gem "spring-commands-spinach", '1.0.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 1413e967416..c847424a7c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -144,11 +144,10 @@ GEM email_spec (1.5.0) launchy (~> 2.1) mail (~> 2.2) - emoji (1.0.1) - json enumerize (0.7.0) activesupport (>= 3.2) equalizer (0.0.8) + errbase (0.0.2) erubis (2.7.0) escape_utils (0.2.4) eventmachine (1.0.4) @@ -190,8 +189,10 @@ GEM dotenv (>= 0.7) thor (>= 0.13.6) formatador (0.2.4) - gemnasium-gitlab-service (0.2.4) + gemnasium-gitlab-service (0.2.5) rugged (~> 0.21) + gemojione (2.0.0) + json gherkin-ruby (0.3.1) racc github-markup (1.3.1) @@ -210,9 +211,9 @@ GEM charlock_holmes (~> 0.6.6) escape_utils (~> 0.2.4) mime-types (~> 1.19) - gitlab_emoji (0.0.1.1) - emoji (~> 1.0.1) - gitlab_git (7.0.0.rc15) + gitlab_emoji (0.1.0) + gemojione (~> 2.0) + gitlab_git (7.1.0) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -277,10 +278,11 @@ GEM html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) - html-pipeline-gitlab (0.1.5) + html-pipeline-gitlab (0.2.0) actionpack (~> 4) - gitlab_emoji (~> 0.0.1) + gitlab_emoji (~> 0.1) html-pipeline (~> 1.11.0) + mime-types sanitize (~> 2.1) http_parser.rb (0.5.3) httparty (0.13.0) @@ -428,6 +430,7 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) + rack-timeout (0.2.0) rails (4.1.9) actionmailer (= 4.1.9) actionpack (= 4.1.9) @@ -481,6 +484,8 @@ GEM rest-client (1.6.7) mime-types (>= 1.16) rinku (1.7.3) + robustly (0.0.3) + errbase rouge (1.7.4) rspec (2.99.0) rspec-core (~> 2.99.0) @@ -516,7 +521,7 @@ GEM rubyntlm (0.4.0) rubypants (0.2.0) rugged (0.21.4) - rugments (1.0.0.beta3) + rugments (1.0.0.beta4) safe_yaml (0.9.7) sanitize (2.1.0) nokogiri (>= 1.4.4) @@ -563,6 +568,9 @@ GEM temple (~> 0.6.6) tilt (>= 1.3.3, < 2.1) slop (3.6.0) + slowpoke (0.0.5) + rack-timeout (>= 0.1.0) + robustly spinach (0.8.7) colorize (= 0.5.8) gherkin-ruby (>= 0.3.1) @@ -570,7 +578,7 @@ GEM capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) - spring (1.3.1) + spring (1.3.3) spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-spinach (1.0.0) @@ -600,7 +608,7 @@ GEM eventmachine (>= 1.0.0) rack (>= 1.0.0) thor (0.19.1) - thread_safe (0.3.4) + thread_safe (0.3.5) tilt (1.4.1) timers (4.0.1) hitimes @@ -700,8 +708,8 @@ DEPENDENCIES gitlab-flowdock-git-hook (~> 0.4.2) gitlab-grack (~> 2.0.0.rc2) gitlab-linguist (~> 3.0.1) - gitlab_emoji (~> 0.0.1.1) - gitlab_git (= 7.0.0.rc15) + gitlab_emoji (~> 0.1) + gitlab_git (= 7.1.0) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.0) gollum-lib (~> 4.0.0) @@ -713,7 +721,7 @@ DEPENDENCIES guard-spinach haml-rails hipchat (~> 1.4.0) - html-pipeline-gitlab (~> 0.1.0) + html-pipeline-gitlab (~> 0.1) httparty jasmine (= 2.0.2) jquery-atwho-rails (~> 0.3.3) @@ -772,8 +780,9 @@ DEPENDENCIES six slack-notifier (~> 1.0.0) slim + slowpoke spinach-rails - spring (= 1.3.1) + spring (~> 1.3.1) spring-commands-rspec (= 1.0.4) spring-commands-spinach (= 1.0.0) stamp diff --git a/app/assets/javascripts/autosave.js.coffee b/app/assets/javascripts/autosave.js.coffee index 3450f4b55f7..5d3fe81da74 100644 --- a/app/assets/javascripts/autosave.js.coffee +++ b/app/assets/javascripts/autosave.js.coffee @@ -14,7 +14,11 @@ class @Autosave restore: -> return unless window.localStorage? - text = window.localStorage.getItem @key + try + text = window.localStorage.getItem @key + catch + return + @field.val text if text?.length > 0 @field.trigger "input" @@ -23,11 +27,13 @@ class @Autosave text = @field.val() if text?.length > 0 - window.localStorage.setItem @key, text + try + window.localStorage.setItem @key, text else @reset() reset: -> return unless window.localStorage? - window.localStorage.removeItem @key
\ No newline at end of file + try + window.localStorage.removeItem @key diff --git a/app/assets/javascripts/blob/blob.js.coffee b/app/assets/javascripts/blob/blob.js.coffee index a5f15f80c5c..37a175fdbc7 100644 --- a/app/assets/javascripts/blob/blob.js.coffee +++ b/app/assets/javascripts/blob/blob.js.coffee @@ -26,7 +26,7 @@ class @BlobView unless isNaN first_line $("#tree-content-holder .highlight .line").removeClass("hll") $("#LC#{line}").addClass("hll") for line in [first_line..last_line] - $.scrollTo("#L#{first_line}") unless e? + $.scrollTo("#L#{first_line}", offset: -50) unless e? # parse selected lines from hash # always return first and last line (initialized to NaN) diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee index 6914ca759f6..2e91a06daa8 100644 --- a/app/assets/javascripts/blob/edit_blob.js.coffee +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -15,7 +15,7 @@ class @EditBlob $(".js-commit-button").click -> $("#file-content").val editor.getValue() $(".file-editor form").submit() - return + return false editModePanes = $(".js-edit-mode-pane") editModeLinks = $(".js-edit-mode a") diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee index a6e27116b40..ab8f98715e8 100644 --- a/app/assets/javascripts/blob/new_blob.js.coffee +++ b/app/assets/javascripts/blob/new_blob.js.coffee @@ -15,7 +15,7 @@ class @NewBlob $(".js-commit-button").click -> $("#file-content").val editor.getValue() $(".file-editor form").submit() - return + return false editor: -> return @editor diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee index 6ef5a539b8f..00ee503ff16 100644 --- a/app/assets/javascripts/dashboard.js.coffee +++ b/app/assets/javascripts/dashboard.js.coffee @@ -1,30 +1,3 @@ class @Dashboard constructor: -> - @initSidebarTab() - - $(".dash-filter").keyup -> - terms = $(this).val() - uiBox = $(this).parents('.panel').first() - if terms == "" || terms == undefined - uiBox.find(".dash-list li").show() - else - uiBox.find(".dash-list li").each (index) -> - name = $(this).find(".filter-title").text() - - if name.toLowerCase().search(terms.toLowerCase()) == -1 - $(this).hide() - else - $(this).show() - - - - initSidebarTab: -> - key = "dashboard_sidebar_filter" - - # store selection in cookie - $('.dash-sidebar-tabs a').on 'click', (e) -> - $.cookie(key, $(e.target).attr('id')) - - # show tab from cookie - sidebar_filter = $.cookie(key) - $("#" + sidebar_filter).tab('show') if sidebar_filter + new ProjectsList() diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 591a3749a93..e1015a63d52 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -52,9 +52,13 @@ class Dispatcher new ZenMode() when 'projects:merge_requests:index' shortcut_handler = new ShortcutsNavigation() + MergeRequests.init() when 'dashboard:show' new Dashboard() new Activities() + when 'dashboard:projects:starred' + new Activities() + new ProjectsList() when 'projects:commit:show' new Commit() new Diff() @@ -62,9 +66,13 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() when 'projects:commits:show' shortcut_handler = new ShortcutsNavigation() - when 'groups:show', 'projects:show' + when 'projects:show' + new Activities() + shortcut_handler = new ShortcutsNavigation() + when 'groups:show' new Activities() shortcut_handler = new ShortcutsNavigation() + new ProjectsList() when 'groups:members' new GroupMembers() new UsersSelect() diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee index e0e7771ab20..be8d225e73b 100644 --- a/app/assets/javascripts/importer_status.js.coffee +++ b/app/assets/javascripts/importer_status.js.coffee @@ -16,20 +16,20 @@ class @ImporterStatus $(".js-import-all").click (event) => $(".js-add-to-import").each -> $(this).click() - + setAutoUpdate: -> setInterval (=> $.get @jobs_url, (data) => $.each data, (i, job) => job_item = $("#project_" + job.id) status_field = job_item.find(".job-status") - + if job.import_status == 'finished' job_item.removeClass("active").addClass("success") - status_field.html('<span class="cgreen"><i class="fa fa-check"></i> done</span>') + status_field.html('<span><i class="fa fa-check"></i> done</span>') else if job.import_status == 'started' status_field.html("<i class='fa fa-spinner fa-spin'></i> started") else status_field.html(job.import_status) - - ), 4000
\ No newline at end of file + + ), 4000 diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 6513f4bcefc..40bb9e9cb0c 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -47,7 +47,7 @@ initSearch: -> @timer = null $("#issue_search").keyup -> - clearTimeout(@timer); + clearTimeout(@timer) @timer = setTimeout(Issues.filterResults, 500) filterResults: => diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee index 9201c84c5ed..83434c1b9ba 100644 --- a/app/assets/javascripts/merge_requests.js.coffee +++ b/app/assets/javascripts/merge_requests.js.coffee @@ -1,8 +1,35 @@ # # * Filter merge requests # -@merge_requestsPage = -> - $('#assignee_id').select2() - $('#milestone_id').select2() - $('#milestone_id, #assignee_id').on 'change', -> - $(this).closest('form').submit() +@MergeRequests = + init: -> + MergeRequests.initSearch() + + # Make sure we trigger ajax request only after user stop typing + initSearch: -> + @timer = null + $("#issue_search").keyup -> + clearTimeout(@timer) + @timer = setTimeout(MergeRequests.filterResults, 500) + + filterResults: => + form = $("#issue_search_form") + search = $("#issue_search").val() + $('.merge-requests-holder').css("opacity", '0.5') + issues_url = form.attr('action') + '? '+ form.serialize() + + $.ajax + type: "GET" + url: form.attr('action') + data: form.serialize() + complete: -> + $('.merge-requests-holder').css("opacity", '1.0') + success: (data) -> + $('.merge-requests-holder').html(data.html) + # Change url so if user reload a page - search results are saved + History.replaceState {page: issues_url}, document.title, issues_url + MergeRequests.reload() + dataType: "json" + + reload: -> + $('#filter_issue_search').val($('#issue_search').val()) diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee index c42f31933d3..d644d50b669 100644 --- a/app/assets/javascripts/milestone.js.coffee +++ b/app/assets/javascripts/milestone.js.coffee @@ -49,6 +49,13 @@ class @Milestone data: data success: (data) -> if data.saved == true + if data.assignee_avatar_url + img_tag = $('<img/>') + img_tag.attr('src', data.assignee_avatar_url) + img_tag.addClass('avatar s16') + $(li).find('.assignee-icon').html(img_tag) + else + $(li).find('.assignee-icon').html('') $(li).effect 'highlight' else new Flash("Issue update failed", 'alert') diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee index d0eaaad92b8..6828ae471e5 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"), { expires: 30 } + $.cookie "default_view", $(e.target).attr("href"), { expires: 30, path: '/' } defaultView = $.cookie("default_view") if defaultView diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee new file mode 100644 index 00000000000..c0e36d1ccc5 --- /dev/null +++ b/app/assets/javascripts/projects_list.js.coffee @@ -0,0 +1,24 @@ +class @ProjectsList + constructor: -> + $(".projects-list .js-expand").on 'click', (e) -> + e.preventDefault() + list = $(this).closest('.projects-list') + list.find("li").show() + list.find("li.bottom").hide() + + $(".projects-list-filter").keyup -> + terms = $(this).val() + uiBox = $(this).closest('.panel') + if terms == "" || terms == undefined + uiBox.find(".projects-list li").show() + else + uiBox.find(".projects-list li").each (index) -> + name = $(this).find(".filter-title").text() + + if name.toLowerCase().search(terms.toLowerCase()) == -1 + $(this).hide() + else + $(this).show() + uiBox.find(".projects-list li.bottom").hide() + + diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee index 8a2e2421c2e..d0d81f96921 100644 --- a/app/assets/javascripts/user.js.coffee +++ b/app/assets/javascripts/user.js.coffee @@ -1,3 +1,4 @@ class @User constructor: -> $('.profile-groups-avatars').tooltip("placement": "top") + new ProjectsList() diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index e5bb5e21bb0..015ff2ce4ec 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -11,12 +11,17 @@ *= require cal-heatmap */ -@import "main/*"; + +@import "base/variables"; +@import "base/mixins"; +@import "base/layout"; + /** * Customized Twitter bootstrap */ -@import 'gl_bootstrap'; +@import 'base/gl_variables'; +@import 'base/gl_bootstrap'; /** * NProgress load bar css @@ -39,7 +44,7 @@ * Page specific styles (issues, projects etc): */ -@import "sections/*"; +@import "pages/*"; /** * Code highlight diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/base/gl_bootstrap.scss index 6efa56544a5..16581e9ebf2 100644 --- a/app/assets/stylesheets/gl_bootstrap.scss +++ b/app/assets/stylesheets/base/gl_bootstrap.scss @@ -3,11 +3,6 @@ * */ -$font-size-base: 13px !default; -$nav-pills-active-link-hover-bg: $bg_primary; -$pagination-active-bg: $bg_primary; -$list-group-active-bg: $bg_primary; - // Core variables and mixins @import "bootstrap/variables"; @import "bootstrap/mixins"; @@ -23,6 +18,7 @@ $list-group-active-bg: $bg_primary; @import "bootstrap/grid"; @import "bootstrap/tables"; @import "bootstrap/forms"; +@import "bootstrap/buttons"; // Components @import "bootstrap/component-animations"; @@ -134,10 +130,6 @@ $list-group-active-bg: $bg_primary; } } } - - &.nav-small-tabs > li > a { - padding: 6px 9px; - } } .nav-tabs > li > a, @@ -145,61 +137,6 @@ $list-group-active-bg: $bg_primary; color: #666; } -.nav-compact > li > a { - padding: 6px 12px; -} - -.nav-small > li > a { - padding: 3px 5px; - font-size: 12px; -} - - -/* - * Callouts from Bootstrap3 docs - * - * Not quite alerts, but custom and helpful notes for folks reading the docs. - * Requires a base and modifier class. - */ - -/* Common styles for all types */ -.bs-callout { - margin: 20px 0; - padding: 20px; - border-left: 3px solid #eee; - color: #666; - background: #f9f9f9; -} -.bs-callout h4 { - margin-top: 0; - margin-bottom: 5px; -} -.bs-callout p:last-child { - margin-bottom: 0; -} - -/* Variations */ -.bs-callout-danger { - background-color: #fdf7f7; - border-color: #eed3d7; - color: #b94a48; -} -.bs-callout-warning { - background-color: #faf8f0; - border-color: #faebcc; - color: #8a6d3b; -} -.bs-callout-info { - background-color: #f4f8fa; - border-color: #bce8f1; - color: #34789a; -} -.bs-callout-success { - background-color: #dff0d8; - border-color: #5cA64d; - color: #3c763d; -} - /** * fix to keep tooltips position in top navigation bar * @@ -214,16 +151,13 @@ $list-group-active-bg: $bg_primary; * */ .panel { - @include border-radius(0px); - .panel-heading { - @include border-radius(0px); font-size: 14px; line-height: 18px; .panel-head-actions { position: relative; - top: -7px; + top: -6px; float: right; } } @@ -256,40 +190,57 @@ $list-group-active-bg: $bg_primary; } } -.panel-default { - .panel-heading { - background-color: #EEE; +.alert { + a { + @extend .alert-link; + color: #fff; + text-decoration: underline; } } -.panel-danger { - @include panel-colored; - .panel-heading { - color: $border_danger; - border-color: $border_danger; - } +// Typography ================================================================= + +.text-primary, +.text-primary:hover { + color: $brand-primary; } -.panel-success { - @include panel-colored; - .panel-heading { - color: $border_success; - border-color: $border_success; - } +.text-success, +.text-success:hover { + color: $brand-success; } -.panel-primary { - @include panel-colored; - .panel-heading { - color: $border_primary; - border-color: $border_primary; - } +.text-danger, +.text-danger:hover { + color: $brand-danger; } -.panel-warning { - @include panel-colored; - .panel-heading { - color: $border_warning; - border-color: $border_warning; +.text-warning, +.text-warning:hover { + color: $brand-warning; +} + +.text-info, +.text-info:hover { + color: $brand-info; +} + +// Tables ===================================================================== + +table.table { + .dropdown-menu a { + text-decoration: none; + } + + .success, + .warning, + .danger, + .info { + color: #fff; + + a:not(.btn) { + text-decoration: underline; + color: #fff; + } } } diff --git a/app/assets/stylesheets/base/gl_variables.scss b/app/assets/stylesheets/base/gl_variables.scss new file mode 100644 index 00000000000..ea230646a89 --- /dev/null +++ b/app/assets/stylesheets/base/gl_variables.scss @@ -0,0 +1,870 @@ +// Override Bootstrap variables here (defaults from bootstrap-sass v3.3.3): + +// +// Variables +// -------------------------------------------------- + + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +// $gray-base: #000 +// $gray-darker: lighten($gray-base, 13.5%) // #222 +// $gray-dark: lighten($gray-base, 20%) // #333 +// $gray: lighten($gray-base, 33.5%) // #555 +// $gray-light: lighten($gray-base, 46.7%) // #777 +// $gray-lighter: lighten($gray-base, 93.5%) // #eee +$gray-base: #000; +$gray-darker: lighten($gray-base, 13.5%); // #222 +$gray-dark: #7b8a8b; // #333 +$gray: #95a5a6; // #555 +$gray-light: #b4bcc2; // #999 +$gray-lighter: #ecf0f1; // #eee + +$brand-primary: $gl-primary; +$brand-success: $gl-success; +$brand-info: $gl-info; +$brand-warning: $gl-warning; +$brand-danger: $gl-danger; + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for `<body>`. +// $body-bg: #fff +//** Global text color on `<body>`. +$text-color: $brand-primary; + +//** Global textual link color. +$link-color: $gl-link-color; +//** Link hover color set via `darken()` function. +// $link-hover-color: darken($link-color, 15%) +//** Link hover decoration. +// $link-hover-decoration: underline + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +// $font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif +// $font-family-serif: Georgia, "Times New Roman", Times, serif +//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`. +// $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace +// $font-family-base: $font-family-sans-serif + +$font-size-base: $gl-font-size; +// $font-size-large: ceil(($font-size-base * 1.25)) // ~18px +// $font-size-small: ceil(($font-size-base * 0.85)) // ~12px + +// $font-size-h1: floor(($font-size-base * 2.6)) // ~36px +// $font-size-h2: floor(($font-size-base * 2.15)) // ~30px +// $font-size-h3: ceil(($font-size-base * 1.7)) // ~24px +// $font-size-h4: ceil(($font-size-base * 1.25)) // ~18px +// $font-size-h5: $font-size-base +// $font-size-h6: ceil(($font-size-base * 0.85)) // ~12px + +//** Unit-less `line-height` for use in components like buttons. +// $line-height-base: 1.428571429 // 20/14 +//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc. +// $line-height-computed: floor(($font-size-base * $line-height-base)) // ~20px + +//** By default, this inherits from the `<body>`. +// $headings-font-family: inherit +// $headings-font-weight: 500 +// $headings-line-height: 1.1 +// $headings-color: inherit + + +//== Iconography +// +//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower. + +//** Load fonts from this directory. + +// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path. +// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths. +// $icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/") + +//** File name for all font files. +// $icon-font-name: "glyphicons-halflings-regular" +//** Element ID within SVG icon file. +// $icon-font-svg-id: "glyphicons_halflingsregular" + + +//== Components +// +//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). + +$padding-base-vertical: 6px; +$padding-base-horizontal: 14px; + +// $padding-large-vertical: 10px +// $padding-large-horizontal: 16px + +// $padding-small-vertical: 5px +// $padding-small-horizontal: 10px + +// $padding-xs-vertical: 1px +// $padding-xs-horizontal: 5px + +// $line-height-large: 1.3333333 // extra decimals for Win 8.1 Chrome +// $line-height-small: 1.5 + +// $border-radius-base: 4px +// $border-radius-large: 6px +// $border-radius-small: 3px + +//** Global color for active items (e.g., navs or dropdowns). +// $component-active-color: #fff +//** Global background color for active items (e.g., navs or dropdowns). +// $component-active-bg: $brand-primary + +//** Width of the `border` for generating carets that indicator dropdowns. +// $caret-width-base: 4px +//** Carets increase slightly in size for larger components. +// $caret-width-large: 5px + + +//== Tables +// +//## Customizes the `.table` component with basic values, each used across all table variations. + +//** Padding for `<th>`s and `<td>`s. +// $table-cell-padding: 8px +//** Padding for cells in `.table-condensed`. +// $table-condensed-cell-padding: 5px + +//** Default background color used for all tables. +// $table-bg: transparent +//** Background color used for `.table-striped`. +// $table-bg-accent: #f9f9f9 +//** Background color used for `.table-hover`. +// $table-bg-hover: #f5f5f5 +// $table-bg-active: $table-bg-hover + +//** Border color for table and cell borders. +// $table-border-color: #ddd + + +//== Buttons +// +//## For each of Bootstrap's buttons, define text, background and border color. + +// $btn-font-weight: normal + +// $btn-default-color: #333 +// $btn-default-bg: #fff +// $btn-default-border: #ccc + +// $btn-primary-color: #fff +// $btn-primary-bg: $brand-primary +// $btn-primary-border: darken($btn-primary-bg, 5%) + +// $btn-success-color: #fff +// $btn-success-bg: $brand-success +// $btn-success-border: darken($btn-success-bg, 5%) + +// $btn-info-color: #fff +// $btn-info-bg: $brand-info +// $btn-info-border: darken($btn-info-bg, 5%) + +// $btn-warning-color: #fff +// $btn-warning-bg: $brand-warning +// $btn-warning-border: darken($btn-warning-bg, 5%) + +// $btn-danger-color: #fff +// $btn-danger-bg: $brand-danger +// $btn-danger-border: darken($btn-danger-bg, 5%) + +// $btn-link-disabled-color: $gray-light + + +//== Forms +// +//## + +//** `<input>` background color +// $input-bg: #fff +//** `<input disabled>` background color +// $input-bg-disabled: $gray-lighter + +//** Text color for `<input>`s +$input-color: $text-color; +//** `<input>` border color +$input-border: #dce4ec; + +// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4 +//** Default `.form-control` border radius +// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS. +// $input-border-radius: $border-radius-base +//** Large `.form-control` border radius +// $input-border-radius-large: $border-radius-large +//** Small `.form-control` border radius +// $input-border-radius-small: $border-radius-small + +//** Border color for inputs on focus +$input-border-focus: $brand-info; + +//** Placeholder text color +// $input-color-placeholder: #999 + +//** Default `.form-control` height +// $input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) +//** Large `.form-control` height +// $input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) +//** Small `.form-control` height +// $input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) + +$legend-color: $text-color; +// $legend-border-color: #e5e5e5 + +//** Background color for textual input addons +// $input-group-addon-bg: $gray-lighter +//** Border color for textual input addons +// $input-group-addon-border-color: $input-border + +//** Disabled cursor for form controls and buttons. +// $cursor-disabled: not-allowed + + +//== Dropdowns +// +//## Dropdown menu container and contents. + +//** Background for the dropdown menu. +// $dropdown-bg: #fff +//** Dropdown menu `border-color`. +// $dropdown-border: rgba(0,0,0,.15) +//** Dropdown menu `border-color` **for IE8**. +// $dropdown-fallback-border: #ccc +//** Divider color for between dropdown items. +// $dropdown-divider-bg: #e5e5e5 + +//** Dropdown link text color. +// $dropdown-link-color: $gray-dark +//** Hover color for dropdown links. +// $dropdown-link-hover-color: darken($gray-dark, 5%) +//** Hover background for dropdown links. +// $dropdown-link-hover-bg: #f5f5f5 + +//** Active dropdown menu item text color. +// $dropdown-link-active-color: $component-active-color +//** Active dropdown menu item background color. +// $dropdown-link-active-bg: $component-active-bg + +//** Disabled dropdown menu item background color. +// $dropdown-link-disabled-color: $gray-light + +//** Text color for headers within dropdown menus. +// $dropdown-header-color: $gray-light + +//** Deprecated `$dropdown-caret-color` as of v3.1.0 +// $dropdown-caret-color: #000 + + +//-- Z-index master list +// +// Warning: Avoid customizing these values. They're used for a bird's eye view +// of components dependent on the z-axis and are designed to all work together. +// +// Note: These variables are not generated into the Customizer. + +// $zindex-navbar: 1000 +// $zindex-dropdown: 1000 +// $zindex-popover: 1060 +// $zindex-tooltip: 1070 +// $zindex-navbar-fixed: 1030 +// $zindex-modal: 1040 + + +//== Media queries breakpoints +// +//## Define the breakpoints at which your layout will change, adapting to different screen sizes. + +// Extra small screen / phone +//** Deprecated `$screen-xs` as of v3.0.1 +// $screen-xs: 480px +//** Deprecated `$screen-xs-min` as of v3.2.0 +// $screen-xs-min: $screen-xs +//** Deprecated `$screen-phone` as of v3.0.1 +// $screen-phone: $screen-xs-min + +// Small screen / tablet +//** Deprecated `$screen-sm` as of v3.0.1 +// $screen-sm: 768px +// $screen-sm-min: $screen-sm +//** Deprecated `$screen-tablet` as of v3.0.1 +// $screen-tablet: $screen-sm-min + +// Medium screen / desktop +//** Deprecated `$screen-md` as of v3.0.1 +// $screen-md: 992px +// $screen-md-min: $screen-md +//** Deprecated `$screen-desktop` as of v3.0.1 +// $screen-desktop: $screen-md-min + +// Large screen / wide desktop +//** Deprecated `$screen-lg` as of v3.0.1 +// $screen-lg: 1200px +// $screen-lg-min: $screen-lg +//** Deprecated `$screen-lg-desktop` as of v3.0.1 +// $screen-lg-desktop: $screen-lg-min + +// So media queries don't overlap when required, provide a maximum +// $screen-xs-max: ($screen-sm-min - 1) +// $screen-sm-max: ($screen-md-min - 1) +// $screen-md-max: ($screen-lg-min - 1) + + +//== Grid system +// +//## Define your custom responsive grid. + +//** Number of columns in the grid. +// $grid-columns: 12 +//** Padding between columns. Gets divided in half for the left and right. +// $grid-gutter-width: 30px +// Navbar collapse +//** Point at which the navbar becomes uncollapsed. +// $grid-float-breakpoint: $screen-sm-min +//** Point at which the navbar begins collapsing. +// $grid-float-breakpoint-max: ($grid-float-breakpoint - 1) + + +//== Container sizes +// +//## Define the maximum width of `.container` for different screen sizes. + +// Small screen / tablet +// $container-tablet: (720px + $grid-gutter-width) +//** For `$screen-sm-min` and up. +// $container-sm: $container-tablet + +// Medium screen / desktop +// $container-desktop: (940px + $grid-gutter-width) +//** For `$screen-md-min` and up. +// $container-md: $container-desktop + +// Large screen / wide desktop +// $container-large-desktop: (1140px + $grid-gutter-width) +//** For `$screen-lg-min` and up. +// $container-lg: $container-large-desktop + + +//== Navbar +// +//## + +// Basics of a navbar +// $navbar-height: 50px +// $navbar-margin-bottom: $line-height-computed +// $navbar-border-radius: $border-radius-base +// $navbar-padding-horizontal: floor(($grid-gutter-width / 2)) +// $navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2) +// $navbar-collapse-max-height: 340px + +// $navbar-default-color: #777 +// $navbar-default-bg: #f8f8f8 +// $navbar-default-border: darken($navbar-default-bg, 6.5%) + +// Navbar links +// $navbar-default-link-color: #777 +// $navbar-default-link-hover-color: #333 +// $navbar-default-link-hover-bg: transparent +// $navbar-default-link-active-color: #555 +// $navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%) +// $navbar-default-link-disabled-color: #ccc +// $navbar-default-link-disabled-bg: transparent + +// Navbar brand label +// $navbar-default-brand-color: $navbar-default-link-color +// $navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%) +// $navbar-default-brand-hover-bg: transparent + +// Navbar toggle +// $navbar-default-toggle-hover-bg: #ddd +// $navbar-default-toggle-icon-bar-bg: #888 +// $navbar-default-toggle-border-color: #ddd + + +// Inverted navbar +// Reset inverted navbar basics +// $navbar-inverse-color: lighten($gray-light, 15%) +// $navbar-inverse-bg: #222 +// $navbar-inverse-border: darken($navbar-inverse-bg, 10%) + +// Inverted navbar links +// $navbar-inverse-link-color: lighten($gray-light, 15%) +// $navbar-inverse-link-hover-color: #fff +// $navbar-inverse-link-hover-bg: transparent +// $navbar-inverse-link-active-color: $navbar-inverse-link-hover-color +// $navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%) +// $navbar-inverse-link-disabled-color: #444 +// $navbar-inverse-link-disabled-bg: transparent + +// Inverted navbar brand label +// $navbar-inverse-brand-color: $navbar-inverse-link-color +// $navbar-inverse-brand-hover-color: #fff +// $navbar-inverse-brand-hover-bg: transparent + +// Inverted navbar toggle +// $navbar-inverse-toggle-hover-bg: #333 +// $navbar-inverse-toggle-icon-bar-bg: #fff +// $navbar-inverse-toggle-border-color: #333 + + +//== Navs +// +//## + +//=== Shared nav styles +// $nav-link-padding: 10px 15px +// $nav-link-hover-bg: $gray-lighter + +// $nav-disabled-link-color: $gray-light +// $nav-disabled-link-hover-color: $gray-light + +//== Tabs +// $nav-tabs-border-color: #ddd + +// $nav-tabs-link-hover-border-color: $gray-lighter + +// $nav-tabs-active-link-hover-bg: $body-bg +// $nav-tabs-active-link-hover-color: $gray +// $nav-tabs-active-link-hover-border-color: #ddd + +// $nav-tabs-justified-link-border-color: #ddd +// $nav-tabs-justified-active-link-border-color: $body-bg + +//== Pills +// $nav-pills-border-radius: $border-radius-base +// $nav-pills-active-link-hover-bg: $component-active-bg +// $nav-pills-active-link-hover-color: $component-active-color + + +//== Pagination +// +//## + +$pagination-color: #fff; +$pagination-bg: $brand-success; +$pagination-border: transparent; + +$pagination-hover-color: #fff; +$pagination-hover-bg: darken($brand-success, 15%); +$pagination-hover-border: transparent; + +$pagination-active-color: #fff; +$pagination-active-bg: darken($brand-success, 15%); +$pagination-active-border: transparent; + +$pagination-disabled-color: #b4bcc2; +$pagination-disabled-bg: lighten($brand-success, 15%); +$pagination-disabled-border: transparent; + + +//== Pager +// +//## + +// $pager-bg: $pagination-bg +// $pager-border: $pagination-border +// $pager-border-radius: 15px + +// $pager-hover-bg: $pagination-hover-bg + +// $pager-active-bg: $pagination-active-bg +// $pager-active-color: $pagination-active-color + +// $pager-disabled-color: $pagination-disabled-color + + +//== Jumbotron +// +//## + +// $jumbotron-padding: 30px +// $jumbotron-color: inherit +// $jumbotron-bg: $gray-lighter +// $jumbotron-heading-color: inherit +// $jumbotron-font-size: ceil(($font-size-base * 1.5)) + + +//== Form states and alerts +// +//## Define colors for form feedback states and, by default, alerts. + + +$state-success-text: #fff; +$state-success-bg: $brand-success; +$state-success-border: $brand-success; + +$state-info-text: #fff; +$state-info-bg: $brand-info; +$state-info-border: $brand-info; + +$state-warning-text: #fff; +$state-warning-bg: $brand-warning; +$state-warning-border: $brand-warning; + +$state-danger-text: #fff; +$state-danger-bg: $brand-danger; +$state-danger-border: $brand-danger; + + +//== Tooltips +// +//## + +//** Tooltip max width +// $tooltip-max-width: 200px +//** Tooltip text color +// $tooltip-color: #fff +//** Tooltip background color +// $tooltip-bg: #000 +// $tooltip-opacity: .9 + +//** Tooltip arrow width +// $tooltip-arrow-width: 5px +//** Tooltip arrow color +// $tooltip-arrow-color: $tooltip-bg + + +//== Popovers +// +//## + +//** Popover body background color +// $popover-bg: #fff +//** Popover maximum width +// $popover-max-width: 276px +//** Popover border color +// $popover-border-color: rgba(0,0,0,.2) +//** Popover fallback border color +// $popover-fallback-border-color: #ccc + +//** Popover title background color +// $popover-title-bg: darken($popover-bg, 3%) + +//** Popover arrow width +// $popover-arrow-width: 10px +//** Popover arrow color +// $popover-arrow-color: $popover-bg + +//** Popover outer arrow width +// $popover-arrow-outer-width: ($popover-arrow-width + 1) +//** Popover outer arrow color +// $popover-arrow-outer-color: fade_in($popover-border-color, 0.05) +//** Popover outer arrow fallback color +// $popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%) + + +//== Labels +// +//## + +//** Default label background color +// $label-default-bg: $gray-light +//** Primary label background color +// $label-primary-bg: $brand-primary +//** Success label background color +// $label-success-bg: $brand-success +//** Info label background color +// $label-info-bg: $brand-info +//** Warning label background color +// $label-warning-bg: $brand-warning +//** Danger label background color +// $label-danger-bg: $brand-danger + +//** Default label text color +// $label-color: #fff +//** Default text color of a linked label +// $label-link-hover-color: #fff + + +//== Modals +// +//## + +//** Padding applied to the modal body +// $modal-inner-padding: 15px + +//** Padding applied to the modal title +// $modal-title-padding: 15px +//** Modal title line-height +// $modal-title-line-height: $line-height-base + +//** Background color of modal content area +// $modal-content-bg: #fff +//** Modal content border color +// $modal-content-border-color: rgba(0,0,0,.2) +//** Modal content border color **for IE8** +// $modal-content-fallback-border-color: #999 + +//** Modal backdrop background color +// $modal-backdrop-bg: #000 +//** Modal backdrop opacity +// $modal-backdrop-opacity: .5 +//** Modal header border color +// $modal-header-border-color: #e5e5e5 +//** Modal footer border color +// $modal-footer-border-color: $modal-header-border-color + +// $modal-lg: 900px +// $modal-md: 600px +// $modal-sm: 300px + + +//== Alerts +// +//## Define alert colors, border radius, and padding. + +// $alert-padding: 15px +$alert-border-radius: 0; +// $alert-link-font-weight: bold + +// $alert-success-bg: $state-success-bg +// $alert-success-text: $state-success-text +// $alert-success-border: $state-success-border + +// $alert-info-bg: $state-info-bg +// $alert-info-text: $state-info-text +// $alert-info-border: $state-info-border + +// $alert-warning-bg: $state-warning-bg +// $alert-warning-text: $state-warning-text +// $alert-warning-border: $state-warning-border + +// $alert-danger-bg: $state-danger-bg +// $alert-danger-text: $state-danger-text +// $alert-danger-border: $state-danger-border + + +//== Progress bars +// +//## + +//** Background color of the whole progress component +// $progress-bg: #f5f5f5 +//** Progress bar text color +// $progress-bar-color: #fff +//** Variable for setting rounded corners on progress bar. +// $progress-border-radius: $border-radius-base + +//** Default progress bar color +// $progress-bar-bg: $brand-primary +//** Success progress bar color +// $progress-bar-success-bg: $brand-success +//** Warning progress bar color +// $progress-bar-warning-bg: $brand-warning +//** Danger progress bar color +// $progress-bar-danger-bg: $brand-danger +//** Info progress bar color +// $progress-bar-info-bg: $brand-info + + +//== List group +// +//## + +//** Background color on `.list-group-item` +// $list-group-bg: #fff +//** `.list-group-item` border color +// $list-group-border: #ddd +//** List group border radius +// $list-group-border-radius: $border-radius-base + +//** Background color of single list items on hover +// $list-group-hover-bg: #f5f5f5 +//** Text color of active list items +// $list-group-active-color: $component-active-color +//** Background color of active list items +// $list-group-active-bg: $component-active-bg +//** Border color of active list elements +// $list-group-active-border: $list-group-active-bg +//** Text color for content within active list items +// $list-group-active-text-color: lighten($list-group-active-bg, 40%) + +//** Text color of disabled list items +// $list-group-disabled-color: $gray-light +//** Background color of disabled list items +// $list-group-disabled-bg: $gray-lighter +//** Text color for content within disabled list items +// $list-group-disabled-text-color: $list-group-disabled-color + +// $list-group-link-color: #555 +// $list-group-link-hover-color: $list-group-link-color +// $list-group-link-heading-color: #333 + + +//== Panels +// +//## + +// $panel-bg: #fff +// $panel-body-padding: 15px +// $panel-heading-padding: 10px 15px +// $panel-footer-padding: $panel-heading-padding +$panel-border-radius: 0; + +//** Border color for elements within panels +// $panel-inner-border: #ddd +// $panel-footer-bg: #f5f5f5 + +$panel-default-text: $text-color; +// $panel-default-border: #ddd +// $panel-default-heading-bg: #f5f5f5 + +// $panel-primary-text: #fff +// $panel-primary-border: $brand-primary +// $panel-primary-heading-bg: $brand-primary + +// $panel-success-text: $state-success-text +// $panel-success-border: $state-success-border +// $panel-success-heading-bg: $state-success-bg + +// $panel-info-text: $state-info-text +// $panel-info-border: $state-info-border +// $panel-info-heading-bg: $state-info-bg + +// $panel-warning-text: $state-warning-text +// $panel-warning-border: $state-warning-border +// $panel-warning-heading-bg: $state-warning-bg + +// $panel-danger-text: $state-danger-text +// $panel-danger-border: $state-danger-border +// $panel-danger-heading-bg: $state-danger-bg + + +//== Thumbnails +// +//## + +//** Padding around the thumbnail image +// $thumbnail-padding: 4px +//** Thumbnail background color +// $thumbnail-bg: $body-bg +//** Thumbnail border color +// $thumbnail-border: #ddd +//** Thumbnail border radius +// $thumbnail-border-radius: $border-radius-base + +//** Custom text color for thumbnail captions +// $thumbnail-caption-color: $text-color +//** Padding around the thumbnail caption +// $thumbnail-caption-padding: 9px + + +//== Wells +// +//## + +$well-bg: $gray-lighter; +$well-border: transparent; + + +//== Badges +// +//## + +// $badge-color: #fff +//** Linked badge text color on hover +// $badge-link-hover-color: #fff +// $badge-bg: $gray-light + +//** Badge text color in active nav link +// $badge-active-color: $link-color +//** Badge background color in active nav link +// $badge-active-bg: #fff + +// $badge-font-weight: bold +// $badge-line-height: 1 +// $badge-border-radius: 10px + + +//== Breadcrumbs +// +//## + +// $breadcrumb-padding-vertical: 8px +// $breadcrumb-padding-horizontal: 15px +//** Breadcrumb background color +// $breadcrumb-bg: #f5f5f5 +//** Breadcrumb text color +// $breadcrumb-color: #ccc +//** Text color of current page in the breadcrumb +// $breadcrumb-active-color: $gray-light +//** Textual separator for between breadcrumb elements +// $breadcrumb-separator: "/" + + +//== Carousel +// +//## + +// $carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6) + +// $carousel-control-color: #fff +// $carousel-control-width: 15% +// $carousel-control-opacity: .5 +// $carousel-control-font-size: 20px + +// $carousel-indicator-active-bg: #fff +// $carousel-indicator-border-color: #fff + +// $carousel-caption-color: #fff + + +//== Close +// +//## + +// $close-font-weight: bold +// $close-color: #000 +// $close-text-shadow: 0 1px 0 #fff + + +//== Code +// +//## + +$code-color: #c7254e; +$code-bg: #f9f2f4; + +$kbd-color: #fff; +$kbd-bg: #333; + +$pre-bg: $gray-lighter; +$pre-color: $text-color; +$pre-border-color: #ccc; +// $pre-scrollable-max-height: 340px + + +//== Type +// +//## + +//** Horizontal offset for forms and lists. +// $component-offset-horizontal: 180px +//** Text muted color +// $text-muted: $gray-light +//** Abbreviations and acronyms border color +// $abbr-border-color: $gray-light +//** Headings small color +$headings-small-color: $gray-dark; +//** Blockquote small color +// $blockquote-small-color: $gray-light +//** Blockquote font size +// $blockquote-font-size: ($font-size-base * 1.25) +//** Blockquote border color +// $blockquote-border-color: $gray-lighter +//** Page header border color +// $page-header-border-color: $gray-lighter +//** Width of horizontal description list titles +// $dl-horizontal-offset: $component-offset-horizontal +//** Horizontal line color. +// $hr-border: $gray-lighter diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/base/layout.scss index 1085e68b7d4..62c11b06368 100644 --- a/app/assets/stylesheets/main/layout.scss +++ b/app/assets/stylesheets/base/layout.scss @@ -4,7 +4,7 @@ html { &.touch .tooltip { display: none !important; } body { - padding-top: 47px; + padding-top: 46px; } } diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/base/mixins.scss index e54482d14c3..ccba65e3fd5 100644 --- a/app/assets/stylesheets/main/mixins.scss +++ b/app/assets/stylesheets/base/mixins.scss @@ -121,14 +121,6 @@ } } -@mixin page-title { - color: #333; - line-height: 1.5; - font-weight: normal; - margin-top: 0px; - margin-bottom: 10px; -} - @mixin str-truncated($max_width: 82%) { display: inline-block; overflow: hidden; @@ -137,14 +129,3 @@ white-space: nowrap; max-width: $max_width; } - -@mixin panel-colored { - border: 1px solid #EEE; - background: $box_bg; - @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); - - .panel-heading { - font-weight: bold; - background-color: $box_bg; - } -} diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss new file mode 100644 index 00000000000..54af78ee082 --- /dev/null +++ b/app/assets/stylesheets/base/variables.scss @@ -0,0 +1,37 @@ +$style_color: #474D57; +$hover: #FFF3EB; +$box_bg: #F9F9F9; +$gl-link-color: #446e9b; +$nprogress-color: #c0392b; +$gl-font-size: 14px; +$list-font-size: 15px; +$sidebar_width: 230px; +$avatar_radius: 50%; +$code_font_size: 13px; +$code_line_height: 1.5; + +/* + * State colors: + */ +$gl-success: #019875; +$gl-danger: #d9534f; +$gl-primary: #446e9b; +$gl-info: #029ACF; +$gl-warning: #EB9532; + +$gl-primary: #2C3E50; +$gl-success: #18BC9C; +$gl-info: #3498DB; +$gl-warning: #F39C12; +$gl-danger: #E74C3C; +/* + * Commit Diff Colors + */ +$added: #63c363; +$deleted: #f77; + +/* + * Fonts + */ +$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; +$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index 3b360275065..0224484d82b 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -1,127 +1,15 @@ .btn { - display: inline-block; - margin-bottom: 0; - font-weight: normal; - text-align: center; - vertical-align: middle; - cursor: pointer; - background-image: none; - border: $btn-border; - white-space: nowrap; - padding: 6px 12px; - font-size: 13px; - line-height: 18px; - border-radius: 4px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; - color: #444444; - background-color: #fff; - text-shadow: none; - - &.hover, - &:hover { - color: #444444; - text-decoration: none; - background-color: #ebebeb; - border-color: #adadad; - } - - &.focus, - &:focus { - color: #444444; - text-decoration: none; - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; - } - - &.active, - &:active { - outline: 0; - background-image: none; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - } - - &.disabled, - &[disabled] { - cursor: not-allowed; - pointer-events: none; - opacity: 0.65; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - } - - &.btn-primary { - color: #ffffff; - background-color: $bg_primary; - border-color: $border_primary; - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #ffffff; - } - } - - &.btn-success { - color: #ffffff; - background-color: $bg_success; - border-color: $border_success; - - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #ffffff; - } - } - - &.btn-danger { - color: #ffffff; - background-color: $bg_danger; - border-color: $border_danger; - - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #ffffff; - } - } - - &.btn-warning { - color: #ffffff; - background-color: $bg_warning; - border-color: $border_warning; - - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #ffffff; - } - } + @extend .btn-default; &.btn-new { @extend .btn-success; } &.btn-create { - @extend .wide; @extend .btn-success; } &.btn-save { - @extend .wide; @extend .btn-primary; } @@ -133,11 +21,6 @@ float: right; } - &.wide { - padding-left: 20px; - padding-right: 20px; - } - &.btn-small { padding: 2px 10px; font-size: 12px; @@ -151,16 +34,16 @@ } &.btn-close { - color: $bg_danger; - border-color: $border_danger; + color: $gl-danger; + border-color: $gl-danger; &:hover { color: #B94A48; } } &.btn-reopen { - color: $bg_success; - border-color: $border_success; + color: $gl-success; + border-color: $gl-success; &:hover { color: #468847; } @@ -174,9 +57,12 @@ } } - &.btn-lg { - font-size: 15px; - line-height: 1.4; + &.btn-save { + @extend .btn-primary; + } + + &.btn-new, &.btn-create { + @extend .btn-success; } } diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 3db821fdf76..af8e90eb1a9 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -24,7 +24,7 @@ .slead { color: #666; - font-size: 14px; + font-size: 15px; margin-bottom: 12px; font-weight: normal; line-height: 24px; @@ -61,7 +61,7 @@ pre { .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { - background: $bg_primary; + background: $gl-primary; color: #FFF } @@ -71,7 +71,7 @@ pre { /** FLASH message **/ .author_link { - color: $link_color; + color: $gl-link-color; } .help li { color:$style_color; } @@ -306,20 +306,8 @@ table { width: 100%; } -.broadcast-message { - padding: 10px; - text-align: center; - background: #555; - color: #BBB; -} - -.broadcast-message-preview { - @extend .broadcast-message; - margin-bottom: 20px; -} - .btn-sign-in { - margin-top: 7px; + margin-top: 5px; text-shadow: none; } @@ -337,8 +325,11 @@ table { overflow-x: auto; } -.footer-links a { - margin-right: 15px; +.footer-links { + margin-bottom: 20px; + a { + margin-right: 15px; + } } .search_box { diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss index c8982cdc00d..19bc11086e9 100644 --- a/app/assets/stylesheets/generic/forms.scss +++ b/app/assets/stylesheets/generic/forms.scss @@ -29,7 +29,7 @@ fieldset legend { padding: 17px 20px 18px; margin-top: 18px; margin-bottom: 18px; - background-color: whitesmoke; + background-color: #ecf0f1; border-top: 1px solid #e5e5e5; } diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/generic/gfm.scss index 1427b6a5ae4..8fac5e534fa 100644 --- a/app/assets/stylesheets/generic/gfm.scss +++ b/app/assets/stylesheets/generic/gfm.scss @@ -3,7 +3,7 @@ */ .issue-form, .merge-request-form, .wiki-form { .description { - height: 20em; + height: 16em; border-top-left-radius: 0; } } diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 2563ab516e2..9558f241b7c 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -11,17 +11,17 @@ margin-right: 5px; &.issue-box-closed { - background-color: $bg_danger; + background-color: $gl-danger; color: #FFF; } &.issue-box-merged { - background-color: $bg_primary; + background-color: $gl-primary; color: #FFF; } &.issue-box-open { - background-color: $bg_success; + background-color: $gl-success; color: #FFF; } diff --git a/app/assets/stylesheets/generic/jquery.scss b/app/assets/stylesheets/generic/jquery.scss index bfbbc7d25e3..871b808bad4 100644 --- a/app/assets/stylesheets/generic/jquery.scss +++ b/app/assets/stylesheets/generic/jquery.scss @@ -41,8 +41,8 @@ } .ui-state-active { - border: 1px solid $bg_primary; - background: $bg_primary; + border: 1px solid $gl-primary; + background: $gl-primary; color: #FFF; } diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss index 5a87cc6c612..eb39b6bb7e9 100644 --- a/app/assets/stylesheets/generic/markdown_area.scss +++ b/app/assets/stylesheets/generic/markdown_area.scss @@ -57,7 +57,6 @@ border: 1px solid #ddd; min-height: 100px; padding: 5px; - font-size: 14px; box-shadow: none; } @@ -77,3 +76,12 @@ } } } + +.markdown-area { + background: #FFF; + border: 1px solid #ddd; + min-height: 100px; + padding: 5px; + box-shadow: none; + width: 100%; +} diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index b3727c33672..1b0e056216f 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -43,8 +43,10 @@ } } - .page-title .new-issue-link { - display: none; + .page-title { + .note_created_ago, .new-issue-link { + display: none; + } } .issue_edited_ago, .note_edited_ago { diff --git a/app/assets/stylesheets/sections/nav_sidebar.scss b/app/assets/stylesheets/generic/nav_sidebar.scss index 335f1379662..c14f12284da 100644 --- a/app/assets/stylesheets/sections/nav_sidebar.scss +++ b/app/assets/stylesheets/generic/nav_sidebar.scss @@ -147,19 +147,27 @@ .collapse-nav a { left: 0px; - padding: 5px 23px 3px 22px; + padding: 7px 23px 3px 22px; } } } .collapse-nav a { position: fixed; - top: 47px; - padding: 5px 13px 3px 13px; + top: 46px; + padding: 5px 13px 5px 13px; left: 197px; + font-size: 13px; background: #EEE; color: black; - border: 1px solid rgba(0,0,0,0.035); + border-left: 1px solid rgba(0,0,0,0.035); + border-right: 1px solid rgba(0,0,0,0.035); +} + +.collapse-nav a:hover { + text-decoration: none; + color: #333; + background: #eaeaea; } @media (max-width: $screen-md-max) { diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss index d85e80a512b..af0ecb192d6 100644 --- a/app/assets/stylesheets/generic/selects.scss +++ b/app/assets/stylesheets/generic/selects.scss @@ -3,9 +3,8 @@ .select2-choice { background: #FFF; border-color: #BBB; - padding: 6px 12px; - font-size: 13px; - line-height: 18px; + padding: 6px 14px; + line-height: 1.42857143; height: auto; .select2-arrow { @@ -20,7 +19,7 @@ } .select2-container-multi .select2-choices .select2-search-field input { - padding: 6px 12px; + padding: 8px 14px; font-size: 13px; line-height: 18px; height: auto; @@ -42,7 +41,7 @@ .select2-results { max-height: 350px; .select2-highlighted { - background: $bg_primary; + background: $gl-primary; } } } diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index c547ebb3aaf..4d940ee6b29 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -2,28 +2,12 @@ * Headers * */ -h1.page-title { - @include page-title; - font-size: 28px; -} - -h2.page-title { - @include page-title; - font-size: 24px; -} - -h3.page-title { - @include page-title; - font-size: 22px; -} - -h4.page-title { +.page-title { margin-top: 0px; -} - -h6 { - color: #888; - text-transform: uppercase; + color: #333; + line-height: 1.5; + font-weight: normal; + margin-bottom: 5px; } /** CODE **/ @@ -36,52 +20,6 @@ pre { } } -/** - * Links - * - */ -a { - outline: none; - color: $link_color; - &:hover { - text-decoration: underline; - color: $link_hover_color; - } - - &:focus { - text-decoration: underline; - } - - &.darken { - color: $style_color; - } - - &.lined { - text-decoration: underline; - &:hover { text-decoration: underline; } - } - - &.gray { - color: gray; - } - - &.supp_diff_link { - text-align: center; - padding: 20px 0; - background: #f1f1f1; - width: 100%; - float: left; - } - - &.neib { - margin-right: 15px; - } -} - -a:focus { - outline: none; -} - .monospace { font-family: $monospace_font; } diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index fcd4d47bace..c8cb18ec35f 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,6 +1,10 @@ /* https://github.com/MozMorris/tomorrow-pygments */ +pre.code.highlight.dark, .code.dark { + background-color: #1d1f21; + color: #c5c8c6; + pre.code, .line-numbers, .line-numbers a { @@ -13,8 +17,8 @@ } // highlight line via anchor - pre.hll { - background-color: #fff !important; + pre .hll { + background-color: #557 !important; } .hll { background-color: #373b41 } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index bcd2e716657..001e8b31020 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -1,6 +1,10 @@ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */ +pre.code.monokai, .code.monokai { + background: #272822; + color: #f8f8f2; + pre.highlight, .line-numbers, .line-numbers a { @@ -13,7 +17,7 @@ } // highlight line via anchor - pre.hll { + pre .hll { background-color: #49483e !important; } diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 4a6b759bd2c..f5b827e7c02 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,6 +1,10 @@ /* https://gist.github.com/qguv/7936275 */ +pre.code.highlight.solarized-dark, .code.solarized-dark { + background-color: #002b36; + color: #93a1a1; + pre.code, .line-numbers, .line-numbers a { @@ -13,8 +17,8 @@ } // highlight line via anchor - pre.hll { - background-color: #073642 !important; + pre .hll { + background-color: #174652 !important; } /* Solarized Dark diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 7254f4d7ac1..6b44c00c305 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -1,6 +1,10 @@ /* https://gist.github.com/qguv/7936275 */ +pre.code.highlight.solarized-light, .code.solarized-light { + background-color: #fdf6e3; + color: #586e75; + pre.code, .line-numbers, .line-numbers a { @@ -13,8 +17,8 @@ } // highlight line via anchor - pre.hll { - background-color: #eee8d5 !important; + pre .hll { + background-color: #ddd8c5 !important; } /* Solarized Light diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 4d6f5dfd91e..a52ffc971d1 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,6 +1,10 @@ /* https://github.com/aahan/pygments-github-style */ +pre.code.highlight.white, .code.white { + background-color: #fff; + color: #333; + pre.highlight, .line-numbers, .line-numbers a { @@ -13,7 +17,7 @@ } // highlight line via anchor - pre.hll { + pre .hll { background-color: #f8eec7 !important; } diff --git a/app/assets/stylesheets/main/fonts.scss b/app/assets/stylesheets/main/fonts.scss deleted file mode 100644 index f945aaca848..00000000000 --- a/app/assets/stylesheets/main/fonts.scss +++ /dev/null @@ -1,3 +0,0 @@ -/** Typo **/ -$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; -$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss deleted file mode 100644 index acbf5be94a3..00000000000 --- a/app/assets/stylesheets/main/variables.scss +++ /dev/null @@ -1,63 +0,0 @@ -/* - * General Colors - */ -$style_color: #474D57; -$hover: #FFF3EB; -$box_bg: #F9F9F9; - -/* - * Link colors - */ -$link_color: #446e9b; -$link_hover_color: darken($link-color, 10%); - -$btn-border: 1px solid #ccc; - -/* - * Success colors (green) - */ -$border_success: #019875; -$bg_success: #019875; - -/* - * Danger colors (red) - */ -$border_danger: #d43f3a; -$bg_danger: #d9534f; - -/* - * Primary colors (blue) - */ -$border_primary: #446e9b; -$bg_primary: #446e9b; - -/* - * Warning colors (yellow) - */ -$bg_warning: #EB9532; -$border_warning: #EB9532; - -/** - * Commit Diff Colors - */ -$added: #63c363; -$deleted: #f77; - -/** - * NProgress customize - */ -$nprogress-color: #c0392b; - -/** - * Font sizes - */ -$list-font-size: 15px; - -/** - * Sidebar navigation width - */ -$sidebar_width: 230px; - -$avatar_radius: 50%; -$code_font_size: 13px; -$code_line_height: 1.5; diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/pages/admin.scss index a51deee7970..144852e7874 100644 --- a/app/assets/stylesheets/sections/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -50,3 +50,14 @@ line-height: 2; } } + +.broadcast-message { + @extend .alert-warning; + padding: 10px; + text-align: center; +} + +.broadcast-message-preview { + @extend .broadcast-message; + margin-bottom: 20px; +} diff --git a/app/assets/stylesheets/sections/commit.scss b/app/assets/stylesheets/pages/commit.scss index 0e2d9571a45..f46d6542c03 100644 --- a/app/assets/stylesheets/sections/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -45,15 +45,6 @@ } } -.commit-committer-link, -.commit-author-link { - font-size: 13px; - color: #555; - &:hover { - color: #999; - } -} - .commit-box { margin: 10px 0; border-top: 1px solid #ddd; diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/pages/commits.scss index 683aca73593..e167d044e47 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -100,6 +100,7 @@ li.commit { .commit-row-info { color: #777; line-height: 24px; + font-size: 13px; a { color: #777; diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index d8fd83d44b7..5a543a852c2 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -23,28 +23,6 @@ } } -.dash-sidebar-tabs { - margin-bottom: 2px; - border: none; - margin: 0 !important; - - li { - &.active { - a { - background-color: #EEE; - border-bottom: 1px solid #EEE !important; - &:hover { - background: #eee; - } - } - } - - a { - border-color: #DDD !important; - } - } -} - .project-row, .group-row { padding: 0 !important; font-size: 14px; @@ -111,17 +89,8 @@ } .dash-new-project { - background: $bg_success; - border: 1px solid $border_success; - - a { - color: #FFF; - } -} - -.dash-new-group { - background: $bg_success; - border: 1px solid $border_success; + background: $gl-success; + border: 1px solid $gl-success; a { color: #FFF; diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/pages/diff.scss index 54311a68852..54311a68852 100644 --- a/app/assets/stylesheets/sections/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/pages/editor.scss index 88aa256e56e..88aa256e56e 100644 --- a/app/assets/stylesheets/sections/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss diff --git a/app/assets/stylesheets/sections/errors.scss b/app/assets/stylesheets/pages/errors.scss index 32d2d7b1dbf..32d2d7b1dbf 100644 --- a/app/assets/stylesheets/sections/errors.scss +++ b/app/assets/stylesheets/pages/errors.scss diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/pages/events.scss index a477359dc88..3e9e36e477e 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -46,7 +46,6 @@ border-bottom: 1px solid #eee; .event-title { @include str-truncated(72%); - color: #333; font-weight: 500; font-size: 14px; .author_name { @@ -54,6 +53,7 @@ } } .event-body { + font-size: 13px; margin-left: 35px; margin-right: 80px; color: #777; @@ -185,11 +185,10 @@ } .event_filter { - li a { + font-size: 13px; padding: 5px 10px; background: rgba(0,0,0,0.045); margin-left: 4px; } - } diff --git a/app/assets/stylesheets/sections/explore.scss b/app/assets/stylesheets/pages/explore.scss index 9b92128624c..9b92128624c 100644 --- a/app/assets/stylesheets/sections/explore.scss +++ b/app/assets/stylesheets/pages/explore.scss diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/pages/graph.scss index 3d878d1e528..3d878d1e528 100644 --- a/app/assets/stylesheets/sections/graph.scss +++ b/app/assets/stylesheets/pages/graph.scss diff --git a/app/assets/stylesheets/sections/groups.scss b/app/assets/stylesheets/pages/groups.scss index e49fe1a9dd6..e49fe1a9dd6 100644 --- a/app/assets/stylesheets/sections/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/pages/header.scss index 26b4d04106e..26b4d04106e 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/pages/header.scss diff --git a/app/assets/stylesheets/sections/help.scss b/app/assets/stylesheets/pages/help.scss index 07c62f98c36..6da7a2511a2 100644 --- a/app/assets/stylesheets/sections/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -12,7 +12,6 @@ color: #888; a { - font-size: 14px; margin-right: 3px; } } @@ -29,7 +28,6 @@ th { padding-top: 15px; - font-size: 14px; line-height: 1.5; color: #333; text-align: left diff --git a/app/assets/stylesheets/sections/import.scss b/app/assets/stylesheets/pages/import.scss index 3df4bb84bd2..3df4bb84bd2 100644 --- a/app/assets/stylesheets/sections/import.scss +++ b/app/assets/stylesheets/pages/import.scss diff --git a/app/assets/stylesheets/sections/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index d8d12338859..d8d12338859 100644 --- a/app/assets/stylesheets/sections/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/pages/issues.scss index b909725bff5..4ea34cc1dac 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -11,6 +11,7 @@ .issue-info { color: #999; + font-size: 13px; } .issue-check { @@ -40,7 +41,7 @@ } .check-all-holder { - height: 32px; + height: 36px; float: left; margin-right: 12px; padding: 6px 15px; diff --git a/app/assets/stylesheets/sections/labels.scss b/app/assets/stylesheets/pages/labels.scss index d1590e42fcb..d1590e42fcb 100644 --- a/app/assets/stylesheets/sections/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/pages/login.scss index d366300511e..d366300511e 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/pages/login.scss diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 0d2d8b0173e..9bd34b7376f 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -96,6 +96,7 @@ .merge-request-info { color: #999; + font-size: 13px; .merge-request-labels { display: inline-block; @@ -136,8 +137,8 @@ background-color: #F5F5F5; &.ci-success { - color: $bg_success; - border-color: $border_success; + color: $gl-success; + border-color: $gl-success; background-color: #F1FAF1; } @@ -148,20 +149,20 @@ } &.ci-running { - color: $bg_warning; - border-color: $border_warning; + color: $gl-warning; + border-color: $gl-warning; background-color: #FAF5F1; } &.ci-failed { - color: $bg_danger; - border-color: $border_danger; + color: $gl-danger; + border-color: $gl-danger; background-color: #FAF1F1; } &.ci-error { - color: $bg_danger; - border-color: $border_danger; + color: $gl-danger; + border-color: $gl-danger; background-color: #FAF1F1; } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss new file mode 100644 index 00000000000..15e3948e402 --- /dev/null +++ b/app/assets/stylesheets/pages/milestone.scss @@ -0,0 +1,9 @@ +.issues-sortable-list .str-truncated { + max-width: 90%; +} + +li.milestone { + h4 { + font-weight: bold; + } +} diff --git a/app/assets/stylesheets/sections/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index a0522030785..a0522030785 100644 --- a/app/assets/stylesheets/sections/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/pages/notes.scss index 40adc8b3ba7..384ff6d740c 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -38,13 +38,11 @@ ul.notes { .author { color: #333; font-weight: bold; - font-size: 14px; &:hover { - color: $link_color; + color: $gl-link-color; } } .author-username { - font-size: 14px; } } @@ -57,9 +55,6 @@ ul.notes { .note { display: block; position:relative; - .attachment { - font-size: 14px; - } .note-body { overflow: auto; .note-text { @@ -70,7 +65,7 @@ ul.notes { a[href*="/uploads/"] { &:before { margin-right: 4px; - + font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; @@ -153,7 +148,6 @@ ul.notes { @extend .cgray; &:hover { - color: $link_hover_color; &.danger { @extend .cred; } } } @@ -181,10 +175,11 @@ ul.notes { background: #FFF; padding: 4px; font-size: 16px; - color: $link_color; + color: $gl-link-color; margin-left: -60px; position: absolute; z-index: 10; + width: 32px; transition: all 0.2s ease; @@ -193,8 +188,9 @@ ul.notes { filter: alpha(opacity=0); &:hover { - font-size: 24px; - background: $bg_primary; + width: 38px; + font-size: 20px; + background: $gl-info; color: #FFF; @include show-add-diff-note; } diff --git a/app/assets/stylesheets/sections/notifications.scss b/app/assets/stylesheets/pages/notifications.scss index f11c5dff4ab..cc273f55222 100644 --- a/app/assets/stylesheets/sections/notifications.scss +++ b/app/assets/stylesheets/pages/notifications.scss @@ -10,13 +10,13 @@ } .ns-part { - color: $bg_primary; + color: $gl-primary; } .ns-watch { - color: $bg_success; + color: $gl-success; } .ns-mute { - color: $bg_danger; + color: $gl-danger; } diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/pages/profile.scss index 0ab62b7ae49..81afe05162f 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -80,6 +80,10 @@ &.violet { background: #548; } + + &.blue { + background: #2980b9; + } } } } diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/pages/projects.scss index 8bad9b139f4..bfd05973d75 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -99,25 +99,6 @@ margin-right: 45px; } - .btn, - .form-control { - border: 1px solid #E1E1E1; - box-shadow: none; - padding: 6px 9px; - } - - .btn { - background: none; - color: $link_color; - - &.active { - background-color: #f5f5f5; - border: 1px solid rgba(0,0,0,0.195); - color: #333; - font-weight: bold; - } - } - .form-control { cursor: auto; @extend .monospace; @@ -267,15 +248,15 @@ ul.nav.nav-projects-tabs { } .vs-public { - color: $bg_primary; + color: $gl-primary; } .vs-internal { - color: $bg_warning; + color: $gl-warning; } .vs-private { - color: $bg_success; + color: $gl-success; } .breadcrumb.repo-breadcrumb { diff --git a/app/assets/stylesheets/sections/search.scss b/app/assets/stylesheets/pages/search.scss index bdaa17ac339..bdaa17ac339 100644 --- a/app/assets/stylesheets/sections/search.scss +++ b/app/assets/stylesheets/pages/search.scss diff --git a/app/assets/stylesheets/sections/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index d79591d9915..d79591d9915 100644 --- a/app/assets/stylesheets/sections/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss diff --git a/app/assets/stylesheets/sections/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index b9be47e7700..b9be47e7700 100644 --- a/app/assets/stylesheets/sections/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss diff --git a/app/assets/stylesheets/sections/themes.scss b/app/assets/stylesheets/pages/themes.scss index e69de29bb2d..e69de29bb2d 100644 --- a/app/assets/stylesheets/sections/themes.scss +++ b/app/assets/stylesheets/pages/themes.scss diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/pages/tree.scss index 60a1c00b04b..ce02cdb1652 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -39,14 +39,9 @@ .tree-item-file-name { max-width: 320px; vertical-align: middle; - a { - &:hover { - color: $link_hover_color; - } - } - i { - color: $bg_primary; + i, a { + color: $gl-link-color; } img { @@ -66,13 +61,18 @@ .tree_author { padding-right: 8px; + + .commit-author-name { + color: gray; + } } .tree_commit { color: gray; .tree-commit-link { - color: #444; + color: gray; + &:hover { text-decoration: underline; } diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss new file mode 100644 index 00000000000..277afa1db9e --- /dev/null +++ b/app/assets/stylesheets/pages/ui_dev_kit.scss @@ -0,0 +1,9 @@ +.gitlab-ui-dev-kit { + > h2 { + font-size: 27px; + border-bottom: 1px solid #CCC; + color: #666; + margin: 30px 0; + font-weight: bold; + } +} diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/pages/votes.scss index ba0a519dca6..ba0a519dca6 100644 --- a/app/assets/stylesheets/sections/votes.scss +++ b/app/assets/stylesheets/pages/votes.scss diff --git a/app/assets/stylesheets/sections/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index dfaeba41cf6..dfaeba41cf6 100644 --- a/app/assets/stylesheets/sections/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss diff --git a/app/assets/stylesheets/sections/markdown_area.scss b/app/assets/stylesheets/sections/markdown_area.scss deleted file mode 100644 index 8ee8eaa4ee7..00000000000 --- a/app/assets/stylesheets/sections/markdown_area.scss +++ /dev/null @@ -1,9 +0,0 @@ -.markdown-area { - background: #FFF; - border: 1px solid #ddd; - min-height: 100px; - padding: 5px; - font-size: 14px; - box-shadow: none; - width: 100%; -} diff --git a/app/assets/stylesheets/sections/milestone.scss b/app/assets/stylesheets/sections/milestone.scss deleted file mode 100644 index d20391e38fd..00000000000 --- a/app/assets/stylesheets/sections/milestone.scss +++ /dev/null @@ -1,3 +0,0 @@ -.issues-sortable-list .str-truncated { - max-width: 70%; -} diff --git a/app/assets/stylesheets/themes/ui_blue.scss b/app/assets/stylesheets/themes/ui_blue.scss new file mode 100644 index 00000000000..cb7980b5a07 --- /dev/null +++ b/app/assets/stylesheets/themes/ui_blue.scss @@ -0,0 +1,6 @@ +/** + * Modern GitLab UI theme + */ +.ui_blue { + @include dark-theme(#BECDE9, #2980b9, #1970a9, #096099); +} diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 65dc027c8eb..e338abeac4c 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -5,12 +5,12 @@ class Admin::GroupsController < Admin::ApplicationController @groups = Group.all @groups = @groups.sort(@sort = params[:sort]) @groups = @groups.search(params[:name]) if params[:name].present? - @groups = @groups.page(params[:page]).per(20) + @groups = @groups.page(params[:page]).per(PER_PAGE) end def show - @members = @group.members.order("access_level DESC").page(params[:members_page]).per(30) - @projects = @group.projects.page(params[:projects_page]).per(30) + @members = @group.members.order("access_level DESC").page(params[:members_page]).per(PER_PAGE) + @projects = @group.projects.page(params[:projects_page]).per(PER_PAGE) end def new diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 2b1fc862b7f..5176a8399ae 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -11,15 +11,15 @@ class Admin::ProjectsController < Admin::ApplicationController @projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) + @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE) end def show if @group - @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(30) + @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(PER_PAGE) end - @project_members = @project.project_members.page(params[:project_members_page]).per(30) + @project_members = @project.project_members.page(params[:project_members_page]).per(PER_PAGE) end def transfer diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index e80cabd6e18..44a3f1379d8 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -45,7 +45,8 @@ class Admin::ServicesController < Admin::ApplicationController :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :build_key, :server, :teamcity_url, :build_type, - :description, :issues_url, :new_issue_url, :restrict_to_branch + :description, :issues_url, :new_issue_url, :restrict_to_branch, + :send_from_committer_email, :disable_diffs ]) end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index ecedb31a7f8..693970e5349 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -24,7 +24,7 @@ class Admin::UsersController < Admin::ApplicationController def block if user.block - redirect_to :back, alert: "Successfully blocked" + redirect_to :back, notice: "Successfully blocked" else redirect_to :back, alert: "Error occurred. User was not blocked" end @@ -32,7 +32,7 @@ class Admin::UsersController < Admin::ApplicationController def unblock if user.activate - redirect_to :back, alert: "Successfully unblocked" + redirect_to :back, notice: "Successfully unblocked" else redirect_to :back, alert: "Error occurred. User was not unblocked" end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index df1a588313e..e284f31f7ee 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,6 +4,8 @@ class ApplicationController < ActionController::Base include Gitlab::CurrentSettings include GitlabRoutingHelper + PER_PAGE = 20 + before_filter :authenticate_user_from_token! before_filter :authenticate_user! before_filter :reject_blocked! diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb index ce9dd50df67..b827639978c 100644 --- a/app/controllers/profiles/groups_controller.rb +++ b/app/controllers/dashboard/groups_controller.rb @@ -1,15 +1,13 @@ -class Profiles::GroupsController < ApplicationController - layout "profile" - +class Dashboard::GroupsController < ApplicationController def index - @user_groups = current_user.group_members.page(params[:page]).per(20) + @user_groups = current_user.group_members.page(params[:page]).per(PER_PAGE) end def leave @users_group = group.group_members.where(user_id: current_user.id).first if can?(current_user, :destroy, @users_group) @users_group.destroy - redirect_to(profile_groups_path, info: "You left #{group.name} group.") + redirect_to(dashboard_groups_path, info: "You left #{group.name} group.") else return render_403 end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index 386e283f3a0..cb51792df16 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -8,7 +8,7 @@ class Dashboard::MilestonesController < ApplicationController else state('active') end @dashboard_milestones = Milestones::GroupService.new(project_milestones).execute - @dashboard_milestones = Kaminari.paginate_array(@dashboard_milestones).page(params[:page]).per(30) + @dashboard_milestones = Kaminari.paginate_array(@dashboard_milestones).page(params[:page]).per(PER_PAGE) end def show diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb new file mode 100644 index 00000000000..56e6fcc41ca --- /dev/null +++ b/app/controllers/dashboard/projects_controller.rb @@ -0,0 +1,27 @@ +class Dashboard::ProjectsController < ApplicationController + before_filter :event_filter + + def starred + @projects = current_user.starred_projects + @projects = @projects.includes(:namespace, :forked_from_project, :tags) + @projects = @projects.sort(@sort = params[:sort]) + @groups = [] + + respond_to do |format| + format.html + + format.json do + load_events + pager_json("events/_events", @events.count) + end + end + end + + private + + def load_events + @events = Event.in_projects(@projects.pluck(:id)) + @events = @event_filter.apply_filter(@events).with_associations + @events = @events.limit(20).offset(params[:offset] || 0) + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 4930029e165..9bd853ed5c7 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -5,19 +5,9 @@ class DashboardController < ApplicationController before_filter :event_filter, only: :show def show - # Fetch only 30 projects. - # If user needs more - point to Dashboard#projects page - @projects_limit = 30 - - @groups = current_user.authorized_groups.order_name_asc - @has_authorized_projects = @projects.count > 0 - @projects_count = @projects.count - @projects = @projects.includes(:namespace).limit(@projects_limit) - + @projects = @projects.includes(:namespace) @last_push = current_user.recent_push - @publicish_project_count = Project.publicish(current_user).count - respond_to do |format| format.html @@ -33,38 +23,15 @@ class DashboardController < ApplicationController end end - def projects - @projects = case params[:scope] - when 'personal' then - current_user.namespace.projects - when 'joined' then - current_user.authorized_projects.joined(current_user) - when 'owned' then - current_user.owned_projects - else - current_user.authorized_projects - end - - @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present? - @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? - @projects = @projects.includes(:namespace, :forked_from_project, :tags) - @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]).per(30) - - @tags = current_user.authorized_projects.tags_on(:tags) - @groups = current_user.authorized_groups - end - def merge_requests @merge_requests = get_merge_requests_collection - @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) @merge_requests = @merge_requests.preload(:author, :target_project) end def issues @issues = get_issues_collection - @issues = @issues.page(params[:page]).per(20) + @issues = @issues.page(params[:page]).per(PER_PAGE) @issues = @issues.preload(:author, :project) respond_to do |format| diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index ada7031fea4..c51a4a211a6 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -8,6 +8,6 @@ class Explore::GroupsController < ApplicationController @groups = GroupsFinder.new.execute(current_user) @groups = @groups.search(params[:search]) if params[:search].present? @groups = @groups.sort(@sort = params[:sort]) - @groups = @groups.page(params[:page]).per(20) + @groups = @groups.page(params[:page]).per(PER_PAGE) end end diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 0e5891ae807..b295f295bb1 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -6,19 +6,22 @@ class Explore::ProjectsController < ApplicationController def index @projects = ProjectsFinder.new.execute(current_user) + @tags = @projects.tags_on(:tags) + @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? + @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.includes(:namespace).page(params[:page]).per(20) + @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) end def trending @trending_projects = TrendingProjectsFinder.new.execute(current_user) - @trending_projects = @trending_projects.page(params[:page]).per(10) + @trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE) end def starred @starred_projects = ProjectsFinder.new.execute(current_user) @starred_projects = @starred_projects.reorder('star_count DESC') - @starred_projects = @starred_projects.page(params[:page]).per(10) + @starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE) end end diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb new file mode 100644 index 00000000000..7f27f2bb734 --- /dev/null +++ b/app/controllers/groups/application_controller.rb @@ -0,0 +1,10 @@ +class Groups::ApplicationController < ApplicationController + + private + + def authorize_admin_group! + unless can?(current_user, :manage_group, group) + return render_404 + end + end +end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index ca88d033878..b083cf5d8c5 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -1,4 +1,4 @@ -class Groups::GroupMembersController < ApplicationController +class Groups::GroupMembersController < Groups::ApplicationController before_filter :group # Authorize @@ -37,12 +37,6 @@ class Groups::GroupMembersController < ApplicationController @group ||= Group.find_by(path: params[:group_id]) end - def authorize_admin_group! - unless can?(current_user, :manage_group, group) - return render_404 - end - end - def member_params params.require(:group_member).permit(:access_level, :user_id) end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 6802e529b54..c46b8fff88f 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -10,7 +10,7 @@ class Groups::MilestonesController < ApplicationController else state('active') end @group_milestones = Milestones::GroupService.new(project_milestones).execute - @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(30) + @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE) end def show diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index d011523c94f..7e336803fbb 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,4 +1,4 @@ -class GroupsController < ApplicationController +class GroupsController < Groups::ApplicationController skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests] respond_to :html before_filter :group, except: [:new, :create] @@ -52,13 +52,13 @@ class GroupsController < ApplicationController def merge_requests @merge_requests = get_merge_requests_collection - @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) @merge_requests = @merge_requests.preload(:author, :target_project) end def issues @issues = get_issues_collection - @issues = @issues.page(params[:page]).per(20) + @issues = @issues.page(params[:page]).per(PER_PAGE) @issues = @issues.preload(:author, :project) respond_to do |format| @@ -132,12 +132,6 @@ class GroupsController < ApplicationController end end - def authorize_admin_group! - unless can?(current_user, :manage_group, group) - return render_404 - end - end - def set_title @title = 'New Group' end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index fc498559d6b..c4d620d87b1 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -15,4 +15,7 @@ class HelpController < ApplicationController def shortcuts end + + def ui + end end diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index 7dc0cac8d4c..edb8bd4160b 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -3,19 +3,17 @@ class Import::BaseController < ApplicationController private def get_or_create_namespace - existing_namespace = Namespace.find_by_path_or_name(@target_namespace) - - if existing_namespace - if existing_namespace.owner == current_user - namespace = existing_namespace - else + begin + namespace = Group.create!(name: @target_namespace, path: @target_namespace, owner: current_user) + namespace.add_owner(current_user) + rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid + namespace = Namespace.find_by_path_or_name(@target_namespace) + unless namespace.owner == current_user @already_been_taken = true return false end - else - namespace = Group.create(name: @target_namespace, path: @target_namespace, owner: current_user) - namespace.add_owner(current_user) - namespace end + + namespace end end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index dc7668ee6fd..8650b6464dc 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -14,7 +14,7 @@ class Import::GithubController < Import::BaseController def status @repos = client.repos client.orgs.each do |org| - @repos += client.repos(org.login) + @repos += client.org_repos(org.login) end @already_added_projects = current_user.created_projects.where(import_type: "github") diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index a7863aba756..1b9a86ee42c 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -43,7 +43,7 @@ class ProfilesController < ApplicationController end def history - @events = current_user.recent_events.page(params[:page]).per(20) + @events = current_user.recent_events.page(params[:page]).per(PER_PAGE) end def update_username diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 690501f3060..f049e96e61d 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController def index @sort = params[:sort] || 'name' @branches = @repository.branches_sorted_by(@sort) - @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(30) + @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE) end def recent diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 79d9910ce87..b64491b4666 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -37,7 +37,7 @@ class Projects::ImportsController < Projects::ApplicationController private def require_no_repo - if @project.repository_exists? + if @project.repository_exists? && !@project.import_in_progress? redirect_to(namespace_project_path(@project.namespace, @project)) and return end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 6a2af08a199..4266bcaef16 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController terms = params['issue_search'] @issues = get_issues_collection @issues = @issues.full_search(terms) if terms.present? - @issues = @issues.page(params[:page]).per(20) + @issues = @issues.page(params[:page]).per(PER_PAGE) respond_to do |format| format.html @@ -93,7 +93,7 @@ class Projects::IssuesController < Projects::ApplicationController end def bulk_update - result = Issues::BulkUpdateService.new(project, current_user, params).execute + result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute redirect_to :back, notice: "#{result[:count]} issues updated" end @@ -141,4 +141,13 @@ class Projects::IssuesController < Projects::ApplicationController :milestone_id, :state_event, :task_num, label_ids: [] ) end + + def bulk_update_params + params.require(:update).permit( + :issues_ids, + :assignee_id, + :milestone_id, + :state_event + ) + end end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 5e31fce4b0e..207a01ed3b0 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -7,7 +7,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index - @labels = @project.labels.page(params[:page]).per(20) + @labels = @project.labels.page(params[:page]).per(PER_PAGE) end def new diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 26d4c51773f..93d79d81661 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -17,8 +17,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] def index + terms = params['issue_search'] @merge_requests = get_merge_requests_collection - @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = @merge_requests.full_search(terms) if terms.present? + @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) + + respond_to do |format| + format.html + format.json do + render json: { + html: view_to_html_string("projects/merge_requests/_merge_requests") + } + end + end end def show @@ -78,10 +89,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute if @merge_request.valid? - redirect_to( - merge_request_path(@merge_request), - notice: 'Merge request was successfully created.' - ) + redirect_to(merge_request_path(@merge_request)) else @source_project = @merge_request.source_project @target_project = @merge_request.target_project @@ -97,8 +105,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController format.js format.html do redirect_to([@merge_request.target_project.namespace.becomes(Namespace), - @merge_request.target_project, @merge_request], - notice: 'Merge request was successfully updated.') + @merge_request.target_project, @merge_request]) + end + format.json do + render json: { + saved: @merge_request.valid?, + assignee_avatar_url: @merge_request.assignee.try(:avatar_url) + } end end else diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index afdb560e73c..b49b549547a 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -18,7 +18,7 @@ class Projects::MilestonesController < Projects::ApplicationController end @milestones = @milestones.includes(:project) - @milestones = @milestones.page(params[:page]).per(20) + @milestones = @milestones.page(params[:page]).per(PER_PAGE) end def new diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 087579de106..570447c746c 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -52,7 +52,8 @@ class Projects::ServicesController < Projects::ApplicationController :build_key, :server, :teamcity_url, :build_type, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :colorize_messages, :channels, - :push_events, :issues_events, :merge_requests_events, :tag_push_events + :push_events, :issues_events, :merge_requests_events, :tag_push_events, + :note_events, :send_from_committer_email, :disable_diffs ) end end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 08c7ce3f37d..c4f27a6d989 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -7,7 +7,7 @@ class Projects::TagsController < Projects::ApplicationController def index sorted = VersionSorter.rsort(@repository.tag_names) - @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(30) + @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE) end def create @@ -27,7 +27,7 @@ class Projects::TagsController < Projects::ApplicationController tag = @repository.find_tag(params[:id]) if tag && @repository.rm_tag(tag.name) - EventCreateService.new.push_ref(@project, current_user, tag, 'rm', 'refs/tags') + EventCreateService.new.push_ref(@project, current_user, tag, 'rm', Gitlab::Git::TAG_REF_PREFIX) end respond_to do |format| diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 3392fbca91e..643167947b9 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -7,7 +7,7 @@ class Projects::WikisController < Projects::ApplicationController before_filter :load_project_wiki def pages - @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(30) + @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE) end def show diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 82b8a1cc13a..0f28794b736 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -105,7 +105,7 @@ class ProjectsController < ApplicationController if request.referer.include?('/admin') redirect_to admin_namespaces_projects_path else - redirect_to projects_dashboard_path + redirect_to dashboard_path end end end @@ -176,11 +176,11 @@ class ProjectsController < ApplicationController end def autocomplete_emojis - Rails.cache.fetch("autocomplete-emoji-#{Emoji::VERSION}") do - Emoji.names.map do |e| + Rails.cache.fetch("autocomplete-emoji-#{Gemojione::VERSION}") do + Emoji.emojis.map do |name, emoji| { - name: e, - path: view_context.image_url("emoji/#{e}.png") + name: name, + path: view_context.image_url("emoji/#{emoji["unicode"]}.png") } end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 6ac048e4b83..ae501362dc2 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -16,7 +16,7 @@ class SnippetsController < ApplicationController layout :determine_layout def index - @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20) + @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(PER_PAGE) end def user_index @@ -28,7 +28,7 @@ class SnippetsController < ApplicationController filter: :by_user, user: @user, scope: params[:scope] }). - page(params[:page]).per(20) + page(params[:page]).per(PER_PAGE) if @user == current_user render 'current_user_index' diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 810ac9f34bd..c5f3da54ea2 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -1,24 +1,15 @@ class UploadsController < ApplicationController - skip_before_filter :authenticate_user!, :reject_blocked! - before_filter :authorize_access + skip_before_filter :authenticate_user! + before_filter :find_model, :authorize_access! def show - unless upload_model && upload_mount - return not_found! - end - - model = upload_model.find(params[:id]) - uploader = model.send(upload_mount) - - if model.respond_to?(:project) && !can?(current_user, :read_project, model.project) - return not_found! - end + uploader = @model.send(upload_mount) unless uploader.file_storage? return redirect_to uploader.url end - unless uploader.file.exists? + unless uploader.file && uploader.file.exists? return not_found! end @@ -28,9 +19,34 @@ class UploadsController < ApplicationController private - def authorize_access - unless params[:mounted_as] == 'avatar' - authenticate_user! && reject_blocked! + def find_model + unless upload_model && upload_mount + return not_found! + end + + @model = upload_model.find(params[:id]) + end + + def authorize_access! + authorized = + case @model + when Project + can?(current_user, :read_project, @model) + when Group + can?(current_user, :read_group, @model) + when Note + can?(current_user, :read_project, @model.project) + else + # No authentication required for user avatars. + true + end + + return if authorized + + if current_user + not_found! + else + authenticate_user! end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a81e41819b7..8ed6d59c20d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -86,15 +86,6 @@ module ApplicationHelper end end - def group_icon(group_path) - group = Group.find_by(path: group_path) - if group && group.avatar.present? - group.avatar.url - else - image_path('no_group_avatar.png') - end - end - def avatar_icon(user_email = '', size = nil) user = User.find_by(email: user_email) diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 4dae96644c8..c25b54eadc6 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -1,20 +1,4 @@ module DashboardHelper - def projects_dashboard_filter_path(options={}) - exist_opts = { - sort: params[:sort], - scope: params[:scope], - group: params[:group], - tag: params[:tag], - visibility_level: params[:visibility_level], - } - - options = exist_opts.merge(options) - - path = request.path - path << "?#{options.to_param}" - path - end - def assigned_issues_dashboard_path issues_dashboard_path(assignee_id: current_user.id) end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 92cc9c426b8..08476f8516e 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -1,3 +1,6 @@ +require 'html/pipeline' +require 'html/pipeline/gitlab' + module EmailsHelper # Google Actions @@ -39,4 +42,26 @@ module EmailsHelper lexer = Rugments::Lexers::Diff.new raw formatter.format(lexer.lex(diffcontent)) end + + def replace_image_links_with_base64(text, project) + # Used pipelines in GitLab: + # GitlabEmailImageFilter - replaces images that have been uploaded as attachments with inline images in emails. + # + # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters + filters = [ + HTML::Pipeline::Gitlab::GitlabEmailImageFilter + ] + + context = { + base_url: File.join(Gitlab.config.gitlab.url, project.path_with_namespace, 'uploads'), + upload_path: File.join(Rails.root, 'public', 'uploads', project.path_with_namespace), + } + + pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline + + result = pipeline.call(text, context) + text = result[:output].to_html(save_with: 0) + + text.html_safe + end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index d38b546e1b2..779cebc0136 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -166,7 +166,7 @@ module EventsHelper def event_note(text) text = first_line_in_markdown(text, 150) - sanitize(text, tags: %w(a img b pre code p)) + sanitize(text, tags: %w(a img b pre code p span)) end def event_commit_title(message) diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb new file mode 100644 index 00000000000..7616fe6bad8 --- /dev/null +++ b/app/helpers/explore_helper.rb @@ -0,0 +1,17 @@ +module ExploreHelper + def explore_projects_filter_path(options={}) + exist_opts = { + sort: params[:sort], + scope: params[:scope], + group: params[:group], + tag: params[:tag], + visibility_level: params[:visibility_level], + } + + options = exist_opts.merge(options) + + path = request.path + path << "?#{options.to_param}" + path + end +end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index ab30f498c01..f8e104b0827 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -31,7 +31,9 @@ module GitlabMarkdownHelper def markdown(text, options={}) unless (@markdown and options == @options) @options = options - gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self, { + gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self, + user_color_scheme_class, + { # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch- filter_html: true, with_toc_data: true, @@ -119,7 +121,7 @@ module GitlabMarkdownHelper end def ignored_protocols - ["http://","https://", "ftp://", "mailto:"] + ["http://","https://", "ftp://", "mailto:", "smb://"] end def rebuild_path(file_path) diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index ac37f909ce9..8518a47a3a0 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -44,4 +44,8 @@ module GitlabRoutingHelper def merge_request_url(entity, *args) namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args) end + + def snippet_url(entity, *args) + namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args) + end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 03fd461a462..2d0d0b494f6 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -40,4 +40,16 @@ module GroupsHelper false end end + + def group_icon(group) + if group.is_a?(String) + group = Group.find_by(path: group) + end + + if group && group.avatar.present? + group.avatar.url + else + image_path('no_group_avatar.png') + end + end end diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 3383b1ae5be..59fdc0d49cc 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -8,4 +8,15 @@ module MilestonesHelper dashboard_milestones_path(opts) end end + + def milestone_progress_bar(milestone) + options = { + class: 'progress-bar progress-bar-success', + style: "width: #{milestone.percent_complete}%;" + } + + content_tag :div, class: 'progress' do + content_tag :div, nil, options + end + end end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 2bcfde62830..b3132a1f3ba 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -28,7 +28,7 @@ module NamespacesHelper def namespace_icon(namespace, size = 40) if namespace.kind_of?(Group) - group_icon(namespace.path) + group_icon(namespace) else avatar_icon(namespace.owner.email, size) end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a5d7372bbe5..2225b110651 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -265,4 +265,14 @@ module ProjectsHelper "success" end end + + def service_field_value(type, value) + return value unless type == 'password' + + if value.present? + "***********" + else + nil + end + end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 4bc40b35f2d..b55129de292 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -16,28 +16,38 @@ module Emails subject: subject("Project was moved")) end - def repository_push_email(project_id, recipient, author_id, branch, compare) + def repository_push_email(project_id, recipient, author_id, branch, compare, reverse_compare = false, send_from_committer_email = false, disable_diffs = false) @project = Project.find(project_id) @author = User.find(author_id) + @reverse_compare = reverse_compare @compare = compare @commits = Commit.decorate(compare.commits) @diffs = compare.diffs - @branch = branch + @branch = Gitlab::Git.ref_name(branch) + @disable_diffs = disable_diffs + + @subject = "[#{@project.path_with_namespace}][#{@branch}] " + if @commits.length > 1 @target_url = namespace_project_compare_url(@project.namespace, @project, - from: @commits.first, - to: @commits.last) - @subject = "#{@commits.length} new commits pushed to repository" + from: Commit.new(@compare.base), + to: Commit.new(@compare.head)) + @subject << "Deleted " if @reverse_compare + @subject << "#{@commits.length} commits: #{@commits.first.title}" else @target_url = namespace_project_commit_url(@project.namespace, @project, @commits.first) - @subject = @commits.first.title + + @subject << "Deleted 1 commit: " if @reverse_compare + @subject << @commits.first.title end - mail(from: sender(author_id), + @disable_footer = true + + mail(from: sender(author_id, send_from_committer_email), to: recipient, - subject: subject(@subject)) + subject: @subject) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 46ead62f75f..ee27879cf40 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -34,21 +34,41 @@ class Notify < ActionMailer::Base ) end + # Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com", + # "corp.company.com" and "company.com". + # Respects set tld length so "company.co.uk" won't match "somethingelse.uk" + def self.allowed_email_domains + domain_parts = Gitlab.config.gitlab.host.split(".") + allowed_domains = [] + begin + allowed_domains << domain_parts.join(".") + domain_parts.shift + end while domain_parts.length > ActionDispatch::Http::URL.tld_length + + allowed_domains + end + private # The default email address to send emails from def default_sender_address address = Mail::Address.new(Gitlab.config.gitlab.email_from) - address.display_name = "GitLab" + address.display_name = Gitlab.config.gitlab.email_display_name address end # Return an email address that displays the name of the sender. # Only the displayed name changes; the actual email address is always the same. - def sender(sender_id) + def sender(sender_id, send_from_user_email = false) if sender = User.find(sender_id) address = default_sender_address address.display_name = sender.name + + sender_domain = sender.email.split("@").last + if send_from_user_email && self.class.allowed_email_domains.include?(sender_domain) + address.address = sender.email + end + address.format end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index f1d918e5457..588668b3d1e 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -2,17 +2,17 @@ # # Table name: application_settings # -# id :integer not null, primary key -# default_projects_limit :integer -# default_branch_protection :integer -# signup_enabled :boolean -# signin_enabled :boolean -# gravatar_enabled :boolean -# twitter_sharing_enabled :boolean -# sign_in_text :text -# created_at :datetime -# updated_at :datetime -# home_page_url :string(255) +# 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) +# default_branch_protection :integer default(2) +# twitter_sharing_enabled :boolean default(TRUE) # class ApplicationSetting < ActiveRecord::Base diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 50be458bf24..74900d4675d 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -99,5 +99,4 @@ module Mentionable preexisting = references(p, original) create_cross_references!(p, a, preexisting) end - end diff --git a/app/models/event.rb b/app/models/event.rb index 5579ab1dbb0..8d20d7ef252 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -190,19 +190,19 @@ class Event < ActiveRecord::Base end def tag? - data[:ref]["refs/tags"] + Gitlab::Git.tag_ref?(data[:ref]) end def branch? - data[:ref]["refs/heads"] + Gitlab::Git.branch_ref?(data[:ref]) end def new_ref? - commit_from =~ /^00000/ + Gitlab::Git.blank_ref?(commit_from) end def rm_ref? - commit_to =~ /^00000/ + Gitlab::Git.blank_ref?(commit_to) end def md_ref? @@ -226,11 +226,11 @@ class Event < ActiveRecord::Base end def branch_name - @branch_name ||= data[:ref].gsub("refs/heads/", "") + @branch_name ||= Gitlab::Git.ref_name(data[:ref]) end def tag_name - @tag_name ||= data[:ref].gsub("refs/tags/", "") + @tag_name ||= Gitlab::Git.ref_name(data[:ref]) end # Max 20 commits from push DESC diff --git a/app/models/identity.rb b/app/models/identity.rb index b2c3792d1ce..440fcd0d052 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -6,6 +6,8 @@ # extern_uid :string(255) # provider :string(255) # user_id :integer +# created_at :datetime +# updated_at :datetime # class Identity < ActiveRecord::Base diff --git a/app/models/note.rb b/app/models/note.rb index e6c258ffbe9..9ca3e4d7e97 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -151,18 +151,41 @@ class Note < ActiveRecord::Base ) end - def create_new_commits_note(noteable, project, author, commits) - commits_text = ActionController::Base.helpers.pluralize(commits.size, 'new commit') + def create_new_commits_note(merge_request, project, author, new_commits, existing_commits = []) + total_count = new_commits.length + existing_commits.length + commits_text = ActionController::Base.helpers.pluralize(total_count, 'commit') body = "Added #{commits_text}:\n\n" - commits.each do |commit| + if existing_commits.length > 0 + commit_ids = + if existing_commits.length == 1 + existing_commits.first.short_id + else + "#{existing_commits.first.short_id}..#{existing_commits.last.short_id}" + end + + commits_text = ActionController::Base.helpers.pluralize(existing_commits.length, 'commit') + + branch = + if merge_request.for_fork? + "#{merge_request.target_project_namespace}:#{merge_request.target_branch}" + else + merge_request.target_branch + end + + message = "* #{commit_ids} - _#{commits_text} from branch `#{branch}`_" + body << message + body << "\n" + end + + new_commits.each do |commit| message = "* #{commit.short_id} - #{commit.title}" body << message body << "\n" end create( - noteable: noteable, + noteable: merge_request, project: project, author: author, note: body, @@ -308,6 +331,10 @@ class Note < ActiveRecord::Base end end + def hook_attrs + attributes + end + def set_diff # First lets find notes with same diff # before iterating over all mr diffs @@ -466,6 +493,10 @@ class Note < ActiveRecord::Base for_merge_request? && for_diff_line? end + def for_project_snippet? + noteable_type == "Snippet" + end + # override to return commits, which are not active record def noteable if for_commit? diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb index 8ad1ad6267a..d52214cdd69 100644 --- a/app/models/project_services/asana_service.rb +++ b/app/models/project_services/asana_service.rb @@ -2,21 +2,21 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # - require 'asana' class AsanaService < Service @@ -77,7 +77,7 @@ automatically inspected. Leave blank to include all branches.' end user = data[:user_name] - branch = data[:ref].gsub('refs/heads/', '') + branch = Gitlab::Git.ref_name(data[:ref]) branch_restriction = restrict_to_branch.to_s diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 02aa7c972e7..fb7e0c0fb0d 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class AssemblaService < Service diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 6c6d74c615e..0100f1e4a10 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class BambooService < CiService diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb index 96428c91711..270863c1576 100644 --- a/app/models/project_services/buildbox_service.rb +++ b/app/models/project_services/buildbox_service.rb @@ -2,20 +2,22 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # + require "addressable/uri" class BuildboxService < CiService diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 2f86fbe7a03..e591afdda64 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class CampfireService < Service @@ -63,7 +64,7 @@ class CampfireService < Service end def build_message(push) - ref = push[:ref].gsub("refs/heads/", "") + ref = Gitlab::Git.ref_name(push[:ref]) before = push[:before] after = push[:after] @@ -71,9 +72,9 @@ class CampfireService < Service message << "[#{project.name_with_namespace}] " message << "#{push[:user_name]} " - if before.include?('000000') + if Gitlab::Git.blank_ref?(before) message << "pushed new branch #{ref} \n" - elsif after.include?('000000') + elsif Gitlab::Git.blank_ref?(after) message << "removed branch #{ref} \n" else message << "pushed #{push[:total_commits_count]} commits to #{ref}. " diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index 646783c8733..c6f6b4952c9 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -2,19 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # # Base class for CI services diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index b29d1c86881..8d25f627870 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -2,15 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # class CustomIssueTrackerService < IssueTrackerService diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index 21041e08a20..acb5e7f1af5 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -2,22 +2,24 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # class EmailsOnPushService < Service + prop_accessor :send_from_committer_email + prop_accessor :disable_diffs prop_accessor :recipients validates :recipients, presence: true, if: :activated? @@ -37,14 +39,27 @@ class EmailsOnPushService < Service %w(push) end - def execute(data) - return unless supported_events.include?(data[:object_kind]) + def execute(push_data) + return unless supported_events.include?(push_data[:object_kind]) - EmailsOnPushWorker.perform_async(project_id, recipients, data) + EmailsOnPushWorker.perform_async(project_id, recipients, push_data, send_from_committer_email?, disable_diffs?) + end + + def send_from_committer_email? + self.send_from_committer_email == "1" + end + + def disable_diffs? + self.disable_diffs == "1" end def fields + domains = Notify.allowed_email_domains.map { |domain| "user@#{domain}" }.join(", ") [ + { type: 'checkbox', name: 'send_from_committer_email', title: "Send from committer", + help: "Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. #{domains})." }, + { type: 'checkbox', name: 'disable_diffs', title: "Disable code diffs", + help: "Don't include possibly sensitive code diffs in notification body." }, { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }, ] end diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 443dca72a8c..99e361dd6ed 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -2,19 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require "flowdock-git-hook" diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 41eedc215d5..4e75bdfc953 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -2,19 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require "gemnasium/gitlab_service" diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 02bf305f8f2..d81623625c9 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -2,19 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # class GitlabCiService < CiService diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 00f8d430fd5..84346350a6c 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class GitlabIssueTrackerService < IssueTrackerService diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index b85863d2f0d..d264a56ebdf 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -2,19 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # class HipchatService < Service @@ -45,7 +45,7 @@ class HipchatService < Service end def supported_events - %w(push) + %w(push issue merge_request note tag_push) end def execute(data) @@ -62,22 +62,39 @@ class HipchatService < Service @gate ||= HipChat::Client.new(token, options) end - def create_message(push) - ref = push[:ref].gsub("refs/heads/", "") + def create_message(data) + object_kind = data[:object_kind] + + message = \ + case object_kind + when "push", "tag_push" + create_push_message(data) + when "issue" + create_issue_message(data) unless is_update?(data) + when "merge_request" + create_merge_request_message(data) unless is_update?(data) + when "note" + create_note_message(data) + end + end + + def create_push_message(push) + ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch' + ref = Gitlab::Git.ref_name(push[:ref]) + before = push[:before] after = push[:after] message = "" message << "#{push[:user_name]} " - if before.include?('000000') - message << "pushed new branch <a href=\""\ - "#{project.web_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"\ - " to <a href=\"#{project.web_url}\">"\ - "#{project.name_with_namespace.gsub!(/\s/, "")}</a>\n" - elsif after.include?('000000') - message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n" + if Gitlab::Git.blank_ref?(before) + message << "pushed new #{ref_type} <a href=\""\ + "#{project_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"\ + " to #{project_link}\n" + elsif Gitlab::Git.blank_ref?(after) + message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n" else - message << "pushed to branch <a href=\""\ + message << "pushed to #{ref_type} <a href=\""\ "#{project.web_url}/commits/#{URI.escape(ref)}\">#{ref}</a> " message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> " message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)" @@ -93,4 +110,129 @@ class HipchatService < Service message end + + def format_body(body) + if body + body = body.truncate(200, separator: ' ', omission: '...') + end + + "<pre>#{body}</pre>" + end + + def create_issue_message(data) + user_name = data[:user][:name] + + obj_attr = data[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + title = obj_attr[:title] + state = obj_attr[:state] + issue_iid = obj_attr[:iid] + issue_url = obj_attr[:url] + description = obj_attr[:description] + + issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>" + message = "#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>" + + if description + description = format_body(description) + message << description + end + + message + end + + def create_merge_request_message(data) + user_name = data[:user][:name] + + obj_attr = data[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + merge_request_id = obj_attr[:iid] + source_branch = obj_attr[:source_branch] + target_branch = obj_attr[:target_branch] + state = obj_attr[:state] + description = obj_attr[:description] + title = obj_attr[:title] + + merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}" + merge_request_link = "<a href=\"#{merge_request_url}\">merge request ##{merge_request_id}</a>" + message = "#{user_name} #{state} #{merge_request_link} in " \ + "#{project_link}: <b>#{title}</b>" + + if description + description = format_body(description) + message << description + end + + message + end + + def format_title(title) + "<b>" + title.lines.first.chomp + "</b>" + end + + def create_note_message(data) + data = HashWithIndifferentAccess.new(data) + user_name = data[:user][:name] + + repo_attr = HashWithIndifferentAccess.new(data[:repository]) + + obj_attr = HashWithIndifferentAccess.new(data[:object_attributes]) + note = obj_attr[:note] + note_url = obj_attr[:url] + noteable_type = obj_attr[:noteable_type] + + case noteable_type + when "Commit" + commit_attr = HashWithIndifferentAccess.new(data[:commit]) + subject_desc = commit_attr[:id] + subject_desc = Commit.truncate_sha(subject_desc) + subject_type = "commit" + title = format_title(commit_attr[:message]) + when "Issue" + subj_attr = HashWithIndifferentAccess.new(data[:issue]) + subject_id = subj_attr[:iid] + subject_desc = "##{subject_id}" + subject_type = "issue" + title = format_title(subj_attr[:title]) + when "MergeRequest" + subj_attr = HashWithIndifferentAccess.new(data[:merge_request]) + subject_id = subj_attr[:iid] + subject_desc = "##{subject_id}" + subject_type = "merge request" + title = format_title(subj_attr[:title]) + when "Snippet" + subj_attr = HashWithIndifferentAccess.new(data[:snippet]) + subject_id = subj_attr[:id] + subject_desc = "##{subject_id}" + subject_type = "snippet" + title = format_title(subj_attr[:title]) + end + + subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>" + message = "#{user_name} commented on #{subject_html} in #{project_link}: " + message << title + + if note + note = format_body(note) + message << note + end + + message + end + + def project_name + project.name_with_namespace.gsub(/\s/, '') + end + + def project_url + project.web_url + end + + def project_link + "<a href=\"#{project_url}\">#{project_name}</a>" + end + + def is_update?(data) + data[:object_attributes][:action] == 'update' + end end diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 14ea5360aa5..2bddb7b881c 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -2,15 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# require 'uri' diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index bfc65b5379f..8e90c44d103 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class IssueTrackerService < Service @@ -29,18 +30,6 @@ class IssueTrackerService < Service 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 @@ -59,9 +48,9 @@ class IssueTrackerService < Service 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'] + project_url: add_issues_tracker_id(issues_tracker['project_url']), + issues_url: add_issues_tracker_id(issues_tracker['issues_url']), + new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url']) } else self.properties = {} @@ -110,15 +99,15 @@ class IssueTrackerService < Service Gitlab.config.issues_tracker[to_param] end - def set_project_url + def add_issues_tracker_id(url) if self.project id = self.project.issues_tracker_id if id - issues_tracker['project_url'].gsub(":issues_tracker_id", id) + url = url.gsub(":issues_tracker_id", id) end end - issues_tracker['project_url'] + url end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 20611eeb60c..fcd9dc2f336 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class JiraService < IssueTrackerService diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index a2fa9788f10..ade9ee97873 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class PivotaltrackerService < Service diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index 586d9e94a95..53edf522e9a 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class PushoverService < Service @@ -87,13 +88,13 @@ class PushoverService < Service def execute(data) return unless supported_events.include?(data[:object_kind]) - ref = data[:ref].gsub('refs/heads/', '') + ref = Gitlab::Git.ref_name(data[:ref]) before = data[:before] after = data[:after] - if before.include?('000000') + if Gitlab::Git.blank_ref?(before) message = "#{data[:user_name]} pushed new branch \"#{ref}\"." - elsif after.include?('000000') + elsif Gitlab::Git.blank_ref?(after) message = "#{data[:user_name]} deleted branch \"#{ref}\"." else message = "#{data[:user_name]} push to branch \"#{ref}\"." diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index f96eae2daa1..dd9ba97ee1f 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class RedmineService < IssueTrackerService diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 64d6f4327b8..36d9874edd3 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class SlackService < Service @@ -43,7 +44,7 @@ class SlackService < Service end def supported_events - %w(push issue merge_request) + %w(push issue merge_request note tag_push) end def execute(data) @@ -63,12 +64,14 @@ class SlackService < Service message = \ case object_kind - when "push" + when "push", "tag_push" PushMessage.new(data) when "issue" IssueMessage.new(data) unless is_update?(data) when "merge_request" MergeMessage.new(data) unless is_update?(data) + when "note" + NoteMessage.new(data) end opt = {} @@ -99,3 +102,4 @@ end require "slack_service/issue_message" require "slack_service/push_message" require "slack_service/merge_message" +require "slack_service/note_message" diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb index e2fed0bb1b9..5af24a80609 100644 --- a/app/models/project_services/slack_service/issue_message.rb +++ b/app/models/project_services/slack_service/issue_message.rb @@ -1,6 +1,6 @@ class SlackService class IssueMessage < BaseMessage - attr_reader :username + attr_reader :user_name attr_reader :title attr_reader :project_name attr_reader :project_url @@ -11,7 +11,7 @@ class SlackService attr_reader :description def initialize(params) - @username = params[:user][:username] + @user_name = params[:user][:name] @project_name = params[:project_name] @project_url = params[:project_url] @@ -34,7 +34,7 @@ class SlackService private def message - "#{username} #{state} issue #{issue_link} in #{project_link}: #{title}" + "#{user_name} #{state} #{issue_link} in #{project_link}: *#{title}*" end def opened_issue? @@ -50,7 +50,7 @@ class SlackService end def issue_link - "[##{issue_iid}](#{issue_url})" + "[issue ##{issue_iid}](#{issue_url})" end end end diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb index 4dcce1d15a0..e792c258f73 100644 --- a/app/models/project_services/slack_service/merge_message.rb +++ b/app/models/project_services/slack_service/merge_message.rb @@ -1,15 +1,16 @@ class SlackService class MergeMessage < BaseMessage - attr_reader :username + attr_reader :user_name attr_reader :project_name attr_reader :project_url attr_reader :merge_request_id attr_reader :source_branch attr_reader :target_branch attr_reader :state + attr_reader :title def initialize(params) - @username = params[:user][:username] + @user_name = params[:user][:name] @project_name = params[:project_name] @project_url = params[:project_url] @@ -19,6 +20,7 @@ class SlackService @source_branch = obj_attr[:source_branch] @target_branch = obj_attr[:target_branch] @state = obj_attr[:state] + @title = format_title(obj_attr[:title]) end def pretext @@ -31,6 +33,10 @@ class SlackService private + def format_title(title) + '*' + title.lines.first.chomp + '*' + end + def message merge_request_message end @@ -40,11 +46,11 @@ class SlackService end def merge_request_message - "#{username} #{state} merge request #{merge_request_link} in #{project_link}" + "#{user_name} #{state} #{merge_request_link} in #{project_link}: #{title}" end def merge_request_link - "[##{merge_request_id}](#{merge_request_url})" + "[merge request ##{merge_request_id}](#{merge_request_url})" end def merge_request_url diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb new file mode 100644 index 00000000000..074478b292d --- /dev/null +++ b/app/models/project_services/slack_service/note_message.rb @@ -0,0 +1,82 @@ +class SlackService + class NoteMessage < BaseMessage + attr_reader :message + attr_reader :user_name + attr_reader :project_name + attr_reader :project_link + attr_reader :note + attr_reader :note_url + attr_reader :title + + def initialize(params) + params = HashWithIndifferentAccess.new(params) + @user_name = params[:user][:name] + @project_name = params[:project_name] + @project_url = params[:project_url] + + obj_attr = params[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + @note = obj_attr[:note] + @note_url = obj_attr[:url] + noteable_type = obj_attr[:noteable_type] + + case noteable_type + when "Commit" + create_commit_note(HashWithIndifferentAccess.new(params[:commit])) + when "Issue" + create_issue_note(HashWithIndifferentAccess.new(params[:issue])) + when "MergeRequest" + create_merge_note(HashWithIndifferentAccess.new(params[:merge_request])) + when "Snippet" + create_snippet_note(HashWithIndifferentAccess.new(params[:snippet])) + end + end + + def attachments + description_message + end + + private + + def format_title(title) + title.lines.first.chomp + end + + def create_commit_note(commit) + commit_sha = commit[:id] + commit_sha = Commit.truncate_sha(commit_sha) + commit_link = "[commit #{commit_sha}](#{@note_url})" + title = format_title(commit[:message]) + @message = "#{@user_name} commented on #{commit_link} in #{project_link}: *#{title}*" + end + + def create_issue_note(issue) + issue_iid = issue[:iid] + note_link = "[issue ##{issue_iid}](#{@note_url})" + title = format_title(issue[:title]) + @message = "#{@user_name} commented on #{note_link} in #{project_link}: *#{title}*" + end + + def create_merge_note(merge_request) + merge_request_id = merge_request[:iid] + merge_request_link = "[merge request ##{merge_request_id}](#{@note_url})" + title = format_title(merge_request[:title]) + @message = "#{@user_name} commented on #{merge_request_link} in #{project_link}: *#{title}*" + end + + def create_snippet_note(snippet) + snippet_id = snippet[:id] + snippet_link = "[snippet ##{snippet_id}](#{@note_url})" + title = format_title(snippet[:title]) + @message = "#{@user_name} commented on #{snippet_link} in #{project_link}: *#{title}*" + end + + def description_message + [{ text: format(@note), color: attachment_color }] + end + + def project_link + "[#{@project_name}](#{@project_url})" + end + end +end diff --git a/app/models/project_services/slack_service/push_message.rb b/app/models/project_services/slack_service/push_message.rb index 2e566bc317b..b26f3e9ddce 100644 --- a/app/models/project_services/slack_service/push_message.rb +++ b/app/models/project_services/slack_service/push_message.rb @@ -6,7 +6,8 @@ class SlackService attr_reader :project_name attr_reader :project_url attr_reader :ref - attr_reader :username + attr_reader :ref_type + attr_reader :user_name def initialize(params) @after = params[:after] @@ -14,8 +15,9 @@ class SlackService @commits = params.fetch(:commits, []) @project_name = params[:project_name] @project_url = params[:project_url] - @ref = params[:ref].gsub('refs/heads/', '') - @username = params[:user_name] + @ref_type = Gitlab::Git.tag_ref?(params[:ref]) ? 'tag' : 'branch' + @ref = Gitlab::Git.ref_name(params[:ref]) + @user_name = params[:user_name] end def pretext @@ -45,15 +47,15 @@ class SlackService end def new_branch_message - "#{username} pushed new branch #{branch_link} to #{project_link}" + "#{user_name} pushed new #{ref_type} #{branch_link} to #{project_link}" end def removed_branch_message - "#{username} removed branch #{ref} from #{project_link}" + "#{user_name} removed #{ref_type} #{ref} from #{project_link}" end def push_message - "#{username} pushed to branch #{branch_link} of #{project_link} (#{compare_link})" + "#{user_name} pushed to #{ref_type} #{branch_link} of #{project_link} (#{compare_link})" end def commit_messages @@ -74,11 +76,11 @@ class SlackService end def new_branch? - before.include?('000000') + Gitlab::Git.blank_ref?(before) end def removed_branch? - after.include?('000000') + Gitlab::Git.blank_ref?(after) end def branch_url diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 686e6225a2e..7403e19da9a 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -2,19 +2,20 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) # push_events :boolean default(TRUE) # issues_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null # class TeamcityService < CiService @@ -131,7 +132,7 @@ class TeamcityService < CiService password: password, } - branch = data[:ref].gsub('refs/heads/', '') + branch = Gitlab::Git.ref_name(data[:ref]) self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", body: "<build branchName=\"#{branch}\">"\ diff --git a/app/models/repository.rb b/app/models/repository.rb index 5b52739df2b..6117db418a7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -146,7 +146,8 @@ class Repository end def timestamps_by_user_log(user) - args = %W(git log --author=#{user.email} --since=#{(Date.today - 1.year).to_s} --branches --pretty=format:%cd --date=short) + author_emails = '(' + user.all_emails.map{ |e| Regexp.escape(e) }.join('|') + ')' + args = %W(git log -E --author=#{author_emails} --since=#{(Date.today - 1.year).to_s} --branches --pretty=format:%cd --date=short) dates = Gitlab::Popen.popen(args, path_to_repo).first.split("\n") if dates.present? diff --git a/app/models/service.rb b/app/models/service.rb index 98bd40ae95e..33734e97c55 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -2,19 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # # To add new service you should build a class inherited from Service @@ -28,6 +28,7 @@ class Service < ActiveRecord::Base default_value_for :issues_events, true default_value_for :merge_requests_events, true default_value_for :tag_push_events, true + default_value_for :note_events, true after_initialize :initialize_properties @@ -42,6 +43,7 @@ class Service < ActiveRecord::Base scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } scope :issue_hooks, -> { where(issues_events: true, active: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } + scope :note_hooks, -> { where(note_events: true, active: true) } def activated? active diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 82c1ab94446..3fb2ec1d66c 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -59,6 +59,10 @@ class Snippet < ActiveRecord::Base content end + def hook_attrs + attributes + end + def size 0 end diff --git a/app/models/user.rb b/app/models/user.rb index 55768a351e3..0d40ac8309e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,51 +2,53 @@ # # Table name: users # -# id :integer not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(255) default(""), not null -# reset_password_token :string(255) -# reset_password_sent_at :datetime -# remember_created_at :datetime -# sign_in_count :integer default(0) -# current_sign_in_at :datetime -# last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) -# created_at :datetime -# updated_at :datetime -# name :string(255) -# admin :boolean default(FALSE), not null -# projects_limit :integer default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) -# theme_id :integer default(1), not null -# bio :string(255) -# failed_attempts :integer default(0) -# locked_at :datetime -# username :string(255) -# can_create_group :boolean default(TRUE), not null -# can_create_team :boolean default(TRUE), not null -# state :string(255) -# color_scheme_id :integer default(1), not null -# notification_level :integer default(1), not null -# password_expires_at :datetime -# created_by_id :integer -# avatar :string(255) -# confirmation_token :string(255) -# confirmed_at :datetime -# confirmation_sent_at :datetime -# unconfirmed_email :string(255) -# hide_no_ssh_key :boolean default(FALSE) -# hide_no_password :boolean default(FALSE) -# website_url :string(255) default(""), not null -# last_credential_check_at :datetime -# github_access_token :string(255) -# notification_email :string(255) -# password_automatically_set :boolean default(FALSE) -# bitbucket_access_token :string(255) +# id :integer not null, primary key +# email :string(255) default(""), not null +# encrypted_password :string(255) default(""), not null +# reset_password_token :string(255) +# reset_password_sent_at :datetime +# remember_created_at :datetime +# sign_in_count :integer default(0) +# current_sign_in_at :datetime +# last_sign_in_at :datetime +# current_sign_in_ip :string(255) +# last_sign_in_ip :string(255) +# created_at :datetime +# updated_at :datetime +# name :string(255) +# admin :boolean default(FALSE), not null +# projects_limit :integer default(10) +# skype :string(255) default(""), not null +# linkedin :string(255) default(""), not null +# twitter :string(255) default(""), not null +# authentication_token :string(255) +# theme_id :integer default(1), not null +# bio :string(255) +# failed_attempts :integer default(0) +# locked_at :datetime +# username :string(255) +# can_create_group :boolean default(TRUE), not null +# can_create_team :boolean default(TRUE), not null +# state :string(255) +# color_scheme_id :integer default(1), not null +# 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 +# confirmation_sent_at :datetime +# unconfirmed_email :string(255) +# hide_no_ssh_key :boolean default(FALSE) +# website_url :string(255) default(""), not null +# github_access_token :string(255) +# gitlab_access_token :string(255) +# notification_email :string(255) +# hide_no_password :boolean default(FALSE) +# password_automatically_set :boolean default(FALSE) +# bitbucket_access_token :string(255) +# bitbucket_access_token_secret :string(255) # require 'carrierwave/orm/activerecord' @@ -152,24 +154,6 @@ class User < ActiveRecord::Base delegate :path, to: :namespace, allow_nil: true, prefix: true state_machine :state, initial: :active do - after_transition any => :blocked do |user, transition| - # Remove user from all projects and - user.project_members.find_each do |membership| - # skip owned resources - next if membership.project.owner == user - - return false unless membership.destroy - end - - # Remove user from all groups - user.group_members.find_each do |membership| - # skip owned resources - next if membership.group.last_owner?(user) - - return false unless membership.destroy - end - end - event :block do transition active: :blocked end @@ -624,7 +608,7 @@ class User < ActiveRecord::Base def contributed_projects_ids Event.where(author_id: self). where("created_at > ?", Time.now - 1.year). - where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)", + where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)", pushed: Event::PUSHED, created: Event::CREATED). reorder(project_id: :desc). select(:project_id). diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 8cd65724cb9..dfc5677c9d4 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -21,7 +21,7 @@ class CreateTagService < BaseService new_tag = repository.find_tag(tag_name) if new_tag - EventCreateService.new.push_ref(project, current_user, new_tag, 'add', 'refs/tags') + EventCreateService.new.push_ref(project, current_user, new_tag, 'add', Gitlab::Git::TAG_REF_PREFIX) push_data = create_push_data(project, current_user, new_tag) project.execute_hooks(push_data.dup, :tag_push_hooks) @@ -41,7 +41,7 @@ class CreateTagService < BaseService def create_push_data(project, user, tag) data = Gitlab::PushDataBuilder. - build(project, user, Gitlab::Git::BLANK_SHA, tag.target, 'refs/tags/' + tag.name, []) + build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", []) data[:object_kind] = "tag_push" data end diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index ba9547b9242..dc52d6d89df 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -62,19 +62,19 @@ class EventCreateService create_event(project, current_user, Event::CREATED) end - def push_ref(project, current_user, ref, action = 'add', prefix = 'refs/heads') + def push_ref(project, current_user, ref, action = 'add', prefix = Gitlab::Git::BRANCH_REF_PREFIX) commit = project.repository.commit(ref.target) if action.to_s == 'add' - before = '00000000' + before = Gitlab::Git::BLANK_SHA after = commit.id else before = commit.id - after = '00000000' + after = Gitlab::Git::BLANK_SHA end data = { - ref: "#{prefix}/#{ref.name}", + ref: "#{prefix}#{ref.name}", before: before, after: after } diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 13def127763..bfabfd7ade3 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -42,8 +42,10 @@ class GitPushService # as a heuristic. This may include more commits than are actually pushed, but # that shouldn't matter because we check for existing cross-references later. @push_commits = project.repository.commits_between(project.default_branch, newrev) + + # don't process commits for the initial push to the default branch + process_commit_messages(ref) end - process_commit_messages(ref) elsif push_to_existing_branch?(ref, oldrev) # Collect data for this git push @push_commits = project.repository.commits_between(oldrev, newrev) @@ -105,30 +107,24 @@ class GitPushService end def push_to_existing_branch?(ref, oldrev) - ref_parts = ref.split('/') - # Return if this is not a push to a branch (e.g. new commits) - ref_parts[1].include?('heads') && oldrev != Gitlab::Git::BLANK_SHA + Gitlab::Git.branch_ref?(ref) && oldrev != Gitlab::Git::BLANK_SHA end def push_to_new_branch?(ref, oldrev) - ref_parts = ref.split('/') - - ref_parts[1].include?('heads') && oldrev == Gitlab::Git::BLANK_SHA + Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev) end def push_remove_branch?(ref, newrev) - ref_parts = ref.split('/') - - ref_parts[1].include?('heads') && newrev == Gitlab::Git::BLANK_SHA + Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev) end def push_to_branch?(ref) - ref.include?('refs/heads') + Gitlab::Git.branch_ref?(ref) end def is_default_branch?(ref) - ref == "refs/heads/#{project.default_branch}" + Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch end def commit_user(commit) diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb index f72a346af6f..c7cd20b6b60 100644 --- a/app/services/issues/bulk_update_service.rb +++ b/app/services/issues/bulk_update_service.rb @@ -1,38 +1,23 @@ module Issues class BulkUpdateService < BaseService def execute - update_data = params[:update] + issues_ids = params.delete(:issues_ids).split(",") + issue_params = params - issues_ids = update_data[:issues_ids].split(",") - milestone_id = update_data[:milestone_id] - assignee_id = update_data[:assignee_id] - status = update_data[:status] - - new_state = nil - - if status.present? - if status == 'closed' - new_state = :close - else - new_state = :reopen - end - end - - opts = {} - opts[:milestone_id] = milestone_id if milestone_id.present? - opts[:assignee_id] = assignee_id if assignee_id.present? + issue_params.delete(:state_event) unless issue_params[:state_event].present? + issue_params.delete(:milestone_id) unless issue_params[:milestone_id].present? + issue_params.delete(:assignee_id) unless issue_params[:assignee_id].present? issues = Issue.where(id: issues_ids) - issues = issues.select { |issue| can?(current_user, :modify_issue, issue) } - issues.each do |issue| - issue.update_attributes(opts) - issue.send new_state if new_state + next unless can?(current_user, :modify_issue, issue) + + Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue) end { - count: issues.count, - success: !issues.count.zero? + count: issues.count, + success: !issues.count.zero? } end end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 96761bec99f..cab8a1e880e 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -1,10 +1,10 @@ module MergeRequests class RefreshService < MergeRequests::BaseService def execute(oldrev, newrev, ref) - return true unless ref =~ /heads/ + return true unless Gitlab::Git.branch_ref?(ref) @oldrev, @newrev = oldrev, newrev - @branch_name = ref.gsub("refs/heads/", "") + @branch_name = Gitlab::Git.ref_name(ref) @fork_merge_requests = @project.fork_merge_requests.opened @commits = @project.repository.commits_between(oldrev, newrev) @@ -82,8 +82,14 @@ module MergeRequests merge_requests = filter_merge_requests(merge_requests) merge_requests.each do |merge_request| + mr_commit_ids = Set.new(merge_request.commits.map(&:id)) + + new_commits, existing_commits = @commits.partition do |commit| + mr_commit_ids.include?(commit.id) + end + Note.create_new_commits_note(merge_request, merge_request.project, - @current_user, @commits) + @current_user, new_commits, existing_commits) end end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index f64006a4edc..e969061f229 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -17,10 +17,22 @@ module Notes note.references.each do |mentioned| Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project) end + + execute_hooks(note) end end note end + + def hook_data(note) + Gitlab::NoteDataBuilder.build(note, current_user) + end + + def execute_hooks(note) + note_data = hook_data(note) + # TODO: Support Webhooks + note.project.execute_services(note_data, :note_hooks) + end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 2fc63b9f4b7..0063b7ce40c 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -268,6 +268,7 @@ class NotificationService # Also remove duplications and nil recipients def reject_muted_users(users, project = nil) users = users.to_a.compact.uniq + users = users.reject(&:blocked?) users.reject do |user| next user.notification.disabled? unless project diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 46f6e91e808..c5d0b08845b 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -41,7 +41,7 @@ class SystemHooksService path_with_namespace: model.path_with_namespace, project_id: model.id, owner_name: owner.name, - owner_email: owner.respond_to?(:email) ? owner.email : nil, + owner_email: owner.respond_to?(:email) ? owner.email : "", project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase }) when User diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index f528d69f431..520f327f4e7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -8,39 +8,47 @@ %fieldset %legend Features .form-group - = f.label :signup_enabled, class: 'control-label' - .col-sm-10 - = f.check_box :signup_enabled, class: 'checkbox' - .form-group - = f.label :signin_enabled, class: 'control-label' - .col-sm-10 - = f.check_box :signin_enabled, class: 'checkbox' - .form-group - = f.label :gravatar_enabled, class: 'control-label' - .col-sm-10 - = f.check_box :gravatar_enabled, class: 'checkbox' - .form-group - = f.label :twitter_sharing_enabled, "Twitter enabled", class: 'control-label' - .col-sm-10 - = f.check_box :twitter_sharing_enabled, class: 'checkbox' - %span.help-block Show users button to share their newly created public or internal projects on twitter + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :signup_enabled do + = f.check_box :signup_enabled + Signin enabled + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :signin_enabled do + = f.check_box :signin_enabled + Signup enabled + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :gravatar_enabled do + = f.check_box :gravatar_enabled + Gravatar enabled + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :twitter_sharing_enabled do + = f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block' + %strong Twitter enabled + %span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter %fieldset %legend Misc .form-group - = f.label :default_projects_limit, class: 'control-label' + = f.label :default_projects_limit, class: 'control-label col-sm-2' .col-sm-10 = f.number_field :default_projects_limit, class: 'form-control' .form-group - = f.label :default_branch_protection, class: 'control-label' + = f.label :default_branch_protection, class: 'control-label col-sm-2' .col-sm-10 = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control' .form-group - = f.label :home_page_url, class: 'control-label' + = f.label :home_page_url, class: 'control-label col-sm-2' .col-sm-10 - = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com' - %span.help-block We will redirect non-logged in users to this page + = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block' + %span.help-block#home_help_block We will redirect non-logged in users to this page .form-group - = f.label :sign_in_text, class: 'control-label' + = f.label :sign_in_text, class: 'control-label col-sm-2' .col-sm-10 = f.text_area :sign_in_text, class: 'form-control', rows: 4 .help-block Markdown enabled diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 931b0c5c107..d1c586328a2 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -32,9 +32,9 @@ %span.light.pull-right = Milestone.count %p - Users who signed in during last 30 days + Active Users %span.light.pull-right - = User.where("current_sign_in_at > ?", 30.days.ago).count + = User.active.count .col-md-4 %h4 Features diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 86a73200609..9e7751830a4 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -14,7 +14,7 @@ .form-group .col-sm-2 .col-sm-10 - .bs-callout.bs-callout-info + .alert.alert-info = render 'shared/group_tips' .form-actions = f.submit 'Create group', class: "btn btn-create" diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index bb7f1972925..3040faa722b 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -12,7 +12,7 @@ Group info: %ul.well-list %li - = image_tag group_icon(@group.path), class: "avatar s60" + = image_tag group_icon(@group), class: "avatar s60" %li %span.light Name: %strong= @group.name diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index b984188eb9d..3a1e61d5d8d 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -44,7 +44,7 @@ Projects (#{@projects.total_count}) .panel-head-actions .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} %span.light sort: - if @sort.present? = sort_options_hash[@sort] @@ -63,7 +63,7 @@ = sort_title_oldest_updated = link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do = sort_title_largest_repo - = link_to 'New Project', new_project_path, class: "btn btn-new" + = link_to 'New Project', new_project_path, class: "btn btn-sm btn-success" %ul.well-list - @projects.each do |project| %li diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml index 62f4001ca66..291e48efc12 100644 --- a/app/views/admin/services/_form.html.haml +++ b/app/views/admin/services/_form.html.haml @@ -10,7 +10,7 @@ - @service.errors.full_messages.each do |msg| %p= msg - if @service.help.present? - .bs-callout + .alert.alert-info = preserve do = markdown @service.help @@ -53,14 +53,16 @@ - @service.fields.each do |field| - name = field[:name] + - title = field[:title] || name.humanize - value = @service.send(name) unless field[:type] == 'password' - type = field[:type] - placeholder = field[:placeholder] - choices = field[:choices] - default_choice = field[:default_choice] + - help = field[:help] .form-group - = f.label name, class: "control-label" + = f.label name, title, class: "control-label" .col-sm-10 - if type == 'text' = f.text_field name, class: "form-control", placeholder: placeholder @@ -72,6 +74,8 @@ = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } - elsif type == 'password' = f.password_field name, class: 'form-control' + - if help + %span.help-block= help .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 4a4f0549ada..35e9fd5154f 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -35,7 +35,7 @@ Users (#{@users.total_count}) .panel-head-actions .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"} %span.light sort: - if @sort.present? = sort_options_hash[@sort] @@ -59,7 +59,7 @@ = link_to admin_users_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated - = link_to 'New User', new_admin_user_path, class: "btn btn-new" + = link_to 'New User', new_admin_user_path, class: "btn btn-new btn-sm" %ul.well-list - @users.each do |user| %li diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 90267897503..90c9f8c2f9b 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -108,45 +108,49 @@ .col-md-6 - unless @user == current_user - if @user.blocked? - .alert.alert-info - %h4 This user is blocked - %p Blocking user has the following effects: - %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li User will be removed from joined projects and groups - %li Personal projects will be left - %li Owned groups will be left - %br - = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", data: { confirm: 'Are you sure?' } + .panel.panel-info + .panel-heading + This user is blocked + .panel-body + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li Personal projects will be left + %li Owned groups will be left + %br + = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - else - .alert.alert-warning - %h4 Block this user - %p Blocking user has the following effects: + .panel.panel-warning + .panel-heading + Block this user + .panel-body + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li User will be removed from joined projects and groups + %li Personal projects will be left + %li Owned groups will be left + %br + = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning" + + .panel.panel-danger + .panel-heading + Remove user + .panel-body + %p Deleting a user has the following effects: %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li User will be removed from joined projects and groups - %li Personal projects will be left - %li Owned groups will be left + %li All user content like authored issues, snippets, comments will be removed + - rp = @user.personal_projects.count + - unless rp.zero? + %li #{pluralize rp, 'personal project'} will be removed and cannot be restored + - if @user.solo_owned_groups.present? + %li + Next groups with all content will be removed: + %strong #{@user.solo_owned_groups.map(&:name).join(', ')} %br - = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-remove" - - .alert.alert-danger - %h4 - Remove user - %p Deleting a user has the following effects: - %ul - %li All user content like authored issues, snippets, comments will be removed - - rp = @user.personal_projects.count - - unless rp.zero? - %li #{pluralize rp, 'personal project'} will be removed and cannot be restored - - if @user.solo_owned_groups.present? - %li - Next groups with all content will be removed: - %strong #{@user.solo_owned_groups.map(&:name).join(', ')} - %br - = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" #profile.tab-pane .row diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml deleted file mode 100644 index e3df43d8892..00000000000 --- a/app/views/dashboard/_groups.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -.panel.panel-default - .panel-heading.clearfix - .input-group - = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' - - if current_user.can_create_group? - .input-group-addon.dash-new-group - = link_to new_group_path, class: "" do - %strong New group - %ul.well-list.dash-list - - groups.each do |group| - %li.group-row - = link_to group_path(id: group.path), class: dom_class(group) do - .dash-project-avatar - = image_tag group_icon(group.path), class: "avatar s40" - %span.group-name.filter-title - = truncate(group.name, length: 35) - %span.arrow - %i.fa.fa-angle-right - - if groups.blank? - %li - .nothing-here-block You have no groups yet. diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml deleted file mode 100644 index fa9179cb249..00000000000 --- a/app/views/dashboard/_project.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -= link_to project_path(project), class: dom_class(project) do - .dash-project-avatar - = project_icon(project, alt: '', class: 'avatar project-avatar s40') - .dash-project-access-icon - = visibility_level_icon(project.visibility_level) - %span.str-truncated - %span.namespace-name - - if project.namespace - = project.namespace.human_name - \/ - %span.project-name.filter-title - = project.name - %span.arrow - %i.fa.fa-angle-right diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 0596738342f..3634b2bfd7b 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -1,25 +1,10 @@ .panel.panel-default .panel-heading.clearfix .input-group - = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' - if current_user.can_create_project? .input-group-addon.dash-new-project = link_to new_project_path do %strong New project - %ul.well-list.dash-list - - projects.each do |project| - %li.project-row - = render "project", project: project - - - if projects.blank? - %li - .nothing-here-block There are no projects here. - - if @projects_count > @projects_limit - %li.bottom - %span.light - #{@projects_limit} of #{pluralize(@projects_count, 'project')} displayed. - .pull-right - = link_to projects_dashboard_path do - Show all - %i.fa.fa-angle-right + = render 'shared/projects_list', projects: @projects, projects_limit: 20 diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml deleted file mode 100644 index d87ca861aed..00000000000 --- a/app/views/dashboard/_projects_filter.html.haml +++ /dev/null @@ -1,100 +0,0 @@ -.dash-projects-filters.append-bottom-20 - .append-right-20 - %ul.nav.nav-tabs - = nav_tab :scope, nil do - = link_to projects_dashboard_filter_path(scope: nil) do - All - = nav_tab :scope, 'personal' do - = link_to projects_dashboard_filter_path(scope: 'personal') do - Personal - = nav_tab :scope, 'joined' do - = link_to projects_dashboard_filter_path(scope: 'joined') do - Joined - = nav_tab :scope, 'owned' do - = link_to projects_dashboard_filter_path(scope: 'owned') do - Owned - - .dropdown.inline.append-right-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-globe - %span.light Visibility: - - if params[:visibility_level].present? - = visibility_level_label(params[:visibility_level].to_i) - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to projects_dashboard_filter_path(visibility_level: nil) do - Any - - Gitlab::VisibilityLevel.values.each do |level| - %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(visibility_level: level) do - = visibility_level_icon(level) - = visibility_level_label(level) - - - if @groups.present? - .dropdown.inline.append-right-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-group - %span.light Group: - - if params[:group].present? - = Group.find_by(name: params[:group]).name - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to projects_dashboard_filter_path(group: nil) do - Any - - @groups.each do |group| - %li{ class: (group.name == params[:group]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(group: group.name) do - = group.name - %small.pull-right - = group.projects.count - - - - - if @tags.present? - .dropdown.inline.append-right-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-tags - %span.light Tags: - - if params[:tag].present? - = params[:tag] - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to projects_dashboard_filter_path(tag: nil) do - Any - - - @tags.each do |tag| - %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(tag: tag.name) do - %i.fa.fa-tag - = tag.name - - .pull-right - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %span.light sort: - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_recently_created - %b.caret - %ul.dropdown-menu - %li - = link_to projects_dashboard_filter_path(sort: sort_value_recently_created) do - = sort_title_recently_created - = link_to projects_dashboard_filter_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created - = link_to projects_dashboard_filter_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to projects_dashboard_filter_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated - = link_to projects_dashboard_filter_path(sort: sort_value_name) do - = sort_title_name diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml index a980f495427..78f695be916 100644 --- a/app/views/dashboard/_sidebar.html.haml +++ b/app/views/dashboard/_sidebar.html.haml @@ -1,18 +1,3 @@ -%ul.nav.nav-tabs.dash-sidebar-tabs - %li.active - = link_to '#projects', 'data-toggle' => 'tab', id: 'sidebar-projects-tab' do - Projects - %span.badge= @projects_count - %li - = link_to '#groups', 'data-toggle' => 'tab', id: 'sidebar-groups-tab' do - Groups - %span.badge= @groups.count - -.tab-content - .tab-pane.active#projects - = render "projects", projects: @projects - .tab-pane#groups - = render "groups", groups: @groups - += render "dashboard/projects", projects: @projects .prepend-top-20 = render 'shared/promo' diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml index 6e76f95b34e..4e7d6639727 100644 --- a/app/views/dashboard/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/_zero_authorized_projects.html.haml @@ -1,3 +1,4 @@ +- publicish_project_count = Project.publicish(current_user).count %h3.page-title Welcome to GitLab! %p.light Self hosted Git management application. %hr @@ -35,7 +36,7 @@ %i.fa.fa-plus New Group --if @publicish_project_count > 0 +-if publicish_project_count > 0 %hr %div .dashboard-intro-icon @@ -43,7 +44,7 @@ .dashboard-intro-text %p.slead There are - %strong= @publicish_project_count + %strong= publicish_project_count public projects on this server. %br Public projects are an easy way to allow everyone to have read-only access. diff --git a/app/views/profiles/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index daf76636ff2..f7df5352512 100644 --- a/app/views/profiles/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -23,10 +23,11 @@ Settings - if can?(current_user, :destroy, user_group) - = link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do + = link_to leave_dashboard_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do %i.fa.fa-sign-out Leave + = image_tag group_icon(group), class: "avatar s40 avatar-tile" = link_to group, class: 'group-name' do %strong= group.name diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index 65fc5898518..caf3b685864 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -28,8 +28,7 @@ = pluralize milestone.merge_requests_count, 'Merge Request' %span.light #{milestone.percent_complete}% complete - .progress.progress-info - .progress-bar{style: "width: #{milestone.percent_complete}%;"} + = milestone_progress_bar(milestone) %div %br - milestone.milestones.each do |milestone| diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index a45a52001be..57cce9ab749 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -39,8 +39,7 @@ #{@dashboard_milestone.closed_items_count} closed – #{@dashboard_milestone.open_items_count} open - .progress.progress-info - .progress-bar{style: "width: #{@dashboard_milestone.percent_complete}%;"} + = milestone_progress_bar(@dashboard_milestone) %ul.nav.nav-tabs %li.active diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml deleted file mode 100644 index 03d4b3d8bbb..00000000000 --- a/app/views/dashboard/projects.html.haml +++ /dev/null @@ -1,60 +0,0 @@ -%h3.page-title - My Projects - - = link_to new_project_path, class: "btn btn-new pull-right" do - %i.fa.fa-plus - New Project - -%p.light - All projects you have access to are listed here. Public projects are not included here unless you are a member -%hr -.side-filters - = render "projects_filter" -.dash-projects - %ul.bordered-list.my-projects.top-list - - @projects.each do |project| - %li.my-project-row - %h4.project-title - .pull-left - = project_icon(project, alt: '', class: 'avatar project-avatar s60') - .project-access-icon - = visibility_level_icon(project.visibility_level) - = link_to project_path(project), class: dom_class(project) do - %strong= project.name_with_namespace - - - if project.forked_from_project - - %small - %i.fa.fa-code-fork - Forked from: - = link_to project.forked_from_project.name_with_namespace, namespace_project_path(project.namespace, project.forked_from_project) - - - if current_user.can_leave_project?(project) - .pull-right - = link_to leave_namespace_project_team_members_path(project.namespace, project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do - %i.fa.fa-sign-out - Leave - - .project-info - .pull-right - - if project.archived? - %span.label - %i.fa.fa-archive - Archived - - project.tags.each do |tag| - %span.label.label-info - %i.fa.fa-tag - = tag.name - - if project.description.present? - %p= truncate project.description, length: 100 - .last-activity - %span.light Last activity: - %span.date= project_last_activity(project) - - - - if @projects.blank? - %li - .nothing-here-block There are no projects here. - .bottom - = paginate @projects, theme: "gitlab" - diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml new file mode 100644 index 00000000000..94de6092563 --- /dev/null +++ b/app/views/dashboard/projects/starred.html.haml @@ -0,0 +1,23 @@ +- if @projects.any? + .dashboard.row + %section.activities.col-md-8 + = render 'dashboard/activities' + %aside.col-md-4 + .panel.panel-default + .panel-heading.clearfix + .input-group + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' + - if current_user.can_create_project? + .input-group-addon.dash-new-project + = link_to new_project_path do + %strong New project + + = render 'shared/projects_list', projects: @projects, + projects_limit: 20, stars: true, avatar: false + + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + +- else + %h3 You dont have starred projects yet + %p.slead Visit project page and press on star icon and it will appear on this page. diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index f973f4829a0..fa8946011b7 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -1,4 +1,4 @@ -- if @has_authorized_projects +- if @projects.any? .dashboard.row %section.activities.col-md-8 = render 'activities' diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml index 3d62d478869..68c19df092d 100644 --- a/app/views/events/_events.html.haml +++ b/app/views/events/_events.html.haml @@ -1 +1 @@ -= render @events += render partial: 'events/event', collection: @events diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml new file mode 100644 index 00000000000..b3963a9d901 --- /dev/null +++ b/app/views/explore/projects/_filter.html.haml @@ -0,0 +1,67 @@ +.pull-left + = form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f| + .form-group + = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" + .form-group + = button_tag 'Search', class: "btn btn-primary wide" + +.pull-right.hidden-sm.hidden-xs + - if current_user + .dropdown.inline.append-right-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-globe + %span.light Visibility: + - if params[:visibility_level].present? + = visibility_level_label(params[:visibility_level].to_i) + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to explore_projects_filter_path(visibility_level: nil) do + Any + - Gitlab::VisibilityLevel.values.each do |level| + %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } + = link_to explore_projects_filter_path(visibility_level: level) do + = visibility_level_icon(level) + = visibility_level_label(level) + + - if @tags.present? + .dropdown.inline.append-right-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-tags + %span.light Tags: + - if params[:tag].present? + = params[:tag] + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to explore_projects_filter_path(tag: nil) do + Any + + - @tags.each do |tag| + %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } + = link_to explore_projects_filter_path(tag: tag.name) do + %i.fa.fa-tag + = tag.name + + .dropdown.inline + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span.light sort: + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + %b.caret + %ul.dropdown-menu + %li + = link_to explore_projects_filter_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to explore_projects_filter_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index cb93b300d6a..5086b58cd03 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,30 +1,5 @@ .clearfix - .pull-left - = form_tag explore_projects_path, method: :get, class: 'form-inline form-tiny' do |f| - .form-group - = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" - .form-group - = button_tag 'Search', class: "btn btn-primary wide" - - .pull-right - .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} - %span.light sort: - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_recently_created - %b.caret - %ul.dropdown-menu - %li - = link_to explore_projects_path(sort: sort_value_recently_created) do - = sort_title_recently_created - = link_to explore_projects_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created - = link_to explore_projects_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to explore_projects_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated + = render 'filter' %hr .public-projects diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index b505760fa8f..6f53e125c47 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -1,23 +1,10 @@ .panel.panel-default - .panel-heading - Projects (#{projects.count}) - - if can? current_user, :create_projects, @group - .panel-head-actions - = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do - %i.fa.fa-plus - New project - %ul.well-list - - if projects.blank? - .nothing-here-block This group has no projects yet - - projects.each do |project| - %li.project-row - = link_to project_path(project), class: dom_class(project) do - .dash-project-avatar - = project_icon(project, alt: '', class: 'avatar s40') - .dash-project-access-icon - = visibility_level_icon(project.visibility_level) - %span.str-truncated - %span.project-name - = project.name - %span.arrow - %i.fa.fa-angle-right + .panel-heading.clearfix + .input-group + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' + - if can? current_user, :create_projects, @group + .input-group-addon.dash-new-project + = link_to new_project_path(namespace_id: @group.id) do + %strong New project + + = render 'shared/projects_list', projects: @projects, projects_limit: 20 diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index c4eb00e8925..838290e4aca 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -12,7 +12,7 @@ .form-group .col-sm-2 .col-sm-10 - = image_tag group_icon(@group.to_param), alt: '', class: 'avatar group-avatar s160' + = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' %p.light - if @group.avatar? You can change your group avatar here diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index 21029c3a07d..6267006f63f 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -8,6 +8,9 @@ %span.cgray= user.username - if user == current_user %span.label.label-success It's you + - if user.blocked? + %label.label.label-danger + %strong Blocked - if show_roles %span.pull-right @@ -19,7 +22,7 @@ %i.fa.fa-pencil-square-o - if can?(current_user, :destroy, member) - if current_user == member.user - = link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to leave_dashboard_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.fa.fa-minus.fa-inverse - else = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index fcbcb309aa7..9febaab04a7 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -36,8 +36,7 @@ = pluralize milestone.merge_requests_count, 'Merge Request' %span.light #{milestone.percent_complete}% complete - .progress.progress-info - .progress-bar{style: "width: #{milestone.percent_complete}%;"} + = milestone_progress_bar(milestone) %div %br - milestone.milestones.each do |milestone| diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index e3606d167ad..dd2d84499ba 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -45,8 +45,7 @@ #{@group_milestone.closed_items_count} closed – #{@group_milestone.open_items_count} open - .progress.progress-info - .progress-bar{style: "width: #{@group_milestone.percent_complete}%;"} + = milestone_progress_bar(@group_milestone) %ul.nav.nav-tabs %li.active diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 8c829654fb0..c95347b3a55 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -4,7 +4,7 @@ projects: - if can? current_user, :manage_group, @group .panel-head-actions - = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do + = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do %i.fa.fa-plus New Project %ul.well-list diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index a453889f744..25efe973d4f 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 group-avatar s90" + = image_tag group_icon(@group), class: "avatar group-avatar s90" .clearfix %h2 = @group.name diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml new file mode 100644 index 00000000000..58de5b7c869 --- /dev/null +++ b/app/views/help/ui.html.haml @@ -0,0 +1,208 @@ +- lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare." + +.gitlab-ui-dev-kit + %h1 GitLab UI development kit + %p.light + Use page inspector in your browser to check element classes and structure + of examples below. + %hr + %ul + %li + = link_to 'Blocks', '#blocks' + %li + = link_to 'Lists', '#lists' + %li + = link_to 'Tables', '#tables' + %li + = link_to 'Buttons', '#buttons' + %li + = link_to 'Panels', '#panels' + %li + = link_to 'Alerts', '#alerts' + %li + = link_to 'Forms', '#forms' + %li + = link_to 'Markdown', '#markdown' + + %h2#blocks Blocks + + %h3 + %code .well + + + .well + %h4 Something + = lorem + + + %h2#lists Lists + + %h3 + %code .well-list + %ul.well-list + %li + One item + %li + One item + %li + One item + + %h3 + %code .panel .well-list + + .panel.panel-default + .panel-heading My list + %ul.well-list + %li + One item + %li + One item + %li + One item + + %h3 + %code .bordered-list + %ul.bordered-list + %li + One item + %li + One item + %li + One item + + + + %h2#tables Tables + + .example + %table.table + %thead + %tr + %th # + %th First Name + %th Last Name + %th Username + %tbody + %tr + %td 1 + %td Mark + %td Otto + %td @mdo + %tr + %td 2 + %td Jacob + %td Thornton + %td @fat + %tr + %td 3 + %td Larry + %td the Bird + %td @twitter + + + %h2#buttons Buttons + + .example + %button.btn.btn-default{:type => "button"} Default + %button.btn.btn-primary{:type => "button"} Primary + %button.btn.btn-success{:type => "button"} Success + %button.btn.btn-info{:type => "button"} Info + %button.btn.btn-warning{:type => "button"} Warning + %button.btn.btn-danger{:type => "button"} Danger + %button.btn.btn-link{:type => "button"} Link + + %h2#panels Panels + + .row + .col-md-6 + .panel.panel-success + .panel-heading Success + .panel-body + = lorem + .panel.panel-primary + .panel-heading Primary + .panel-body + = lorem + .panel.panel-info + .panel-heading Info + .panel-body + = lorem + .col-md-6 + .panel.panel-warning + .panel-heading Warning + .panel-body + = lorem + .panel.panel-danger + .panel-heading Danger + .panel-body + = lorem + + %h2#alert Alerts + + .row + .col-md-6 + .alert.alert-success + = lorem + .alert.alert-primary + = lorem + .alert.alert-info + = lorem + .col-md-6 + .alert.alert-warning + = lorem + .alert.alert-danger + = lorem + + %h2#forms Forms + + %h3 + %code form.horizontal-form + + %form.form-horizontal + .form-group + %label.col-sm-2.control-label{:for => "inputEmail3"} Email + .col-sm-10 + %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/ + .form-group + %label.col-sm-2.control-label{:for => "inputPassword3"} Password + .col-sm-10 + %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/ + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + %label + %input{:type => "checkbox"}/ + Remember me + .form-group + .col-sm-offset-2.col-sm-10 + %button.btn.btn-default{:type => "submit"} Sign in + + %h3 + %code form + + %form + .form-group + %label{:for => "exampleInputEmail1"} Email address + %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/ + .form-group + %label{:for => "exampleInputPassword1"} Password + %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/ + .checkbox + %label + %input{:type => "checkbox"}/ + Remember me + %button.btn.btn-default{:type => "submit"} Sign in + + %h2#markdown Markdown + %h3 + %code .md or .wiki and others + + Markdown rendering has a bit different css and presented in next UI elements: + + %ul + %li comment + %li issue, merge request description + %li wiki page + %li help page + + You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown", "markdown")}. diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index bcbbaadf3e0..9da3c920c62 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -23,7 +23,7 @@ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' - %span.cgreen + %span %i.fa.fa-check done - elsif project.import_status == 'started' diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 883090a3026..9c4d91013ec 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -23,7 +23,7 @@ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' - %span.cgreen + %span %i.fa.fa-check done - elsif project.import_status == 'started' diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 41ac073eae1..e809643d8d4 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -23,7 +23,7 @@ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' - %span.cgreen + %span %i.fa.fa-check done - elsif project.import_status == 'started' diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index ebe24747a05..645241a6c69 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -23,7 +23,7 @@ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' - %span.cgreen + %span %i.fa.fa-check done - elsif project.import_status == 'started' diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index bd6bb3c720d..3d6d2bfc00a 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -12,7 +12,7 @@ - unless current_controller?('sessions') .pull-right.hidden-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new' + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10' .navbar-collapse.collapse %ul.nav.navbar-nav diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 304744ba251..e4f630c6a18 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -3,12 +3,17 @@ = link_to root_path, title: 'Home', class: 'shortcuts-activity' do %i.fa.fa-dashboard %span - Activity - = nav_link(path: 'dashboard#projects') do - = link_to projects_dashboard_path, title: 'Projects', class: 'shortcuts-projects' do - %i.fa.fa-cube + Your Projects + = nav_link(path: 'projects#starred') do + = link_to starred_dashboard_projects_path, title: 'Starred Projects' do + %i.fa.fa-star %span - Projects + Starred Projects + = nav_link(controller: :groups) do + = link_to dashboard_groups_path, title: 'Groups' do + %i.fa.fa-group + %span + Groups = nav_link(controller: :milestones) do = link_to dashboard_milestones_path, title: 'Milestones' do %i.fa.fa-clock-o @@ -31,4 +36,3 @@ %i.fa.fa-question-circle %span Help - diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 0914d2a167a..d88e862829d 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -43,11 +43,6 @@ %i.fa.fa-image %span Design - = nav_link(controller: :groups) 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, title: 'History' do %i.fa.fa-history diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 8cca80e5248..7eec93abdf6 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -16,6 +16,18 @@ font-size:small; color:#777 } + pre.commit-message { + white-space: pre-wrap; + } + .file-stats a { + text-decoration: none; + } + .file-stats .new-file { + color: #090; + } + .file-stats .deleted-file { + color: #B00; + } #{add_email_highlight_css} %body %div.content @@ -27,5 +39,5 @@ - if @target_url #{link_to "View it on GitLab", @target_url} = email_action @target_url - - if @project + - if @project && !@disable_footer You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team. diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml index 5272dfa0ede..778a78acf56 100644 --- a/app/views/notify/_note_message.html.haml +++ b/app/views/notify/_note_message.html.haml @@ -1,2 +1,2 @@ %div - = markdown(@note.note) + = replace_image_links_with_base64(markdown(@note.note), @note.project) diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml index f2f8eee18c4..03cbee94608 100644 --- a/app/views/notify/new_issue_email.html.haml +++ b/app/views/notify/new_issue_email.html.haml @@ -1,5 +1,5 @@ -if @issue.description - = markdown(@issue.description) + = replace_image_links_with_base64(markdown(@issue.description), @issue.project) - if @issue.assignee_id.present? %p diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index f02d5111b22..729a7bb505d 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -6,4 +6,4 @@ Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} -if @merge_request.description - = markdown(@merge_request.description) + = replace_image_links_with_base64(markdown(@merge_request.description), @merge_request.project) diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index a45d1dedcd1..039b92df2be 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -1,6 +1,12 @@ %h3 #{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} -%h4 Commits: +- if @reverse_compare + %p + %strong WARNING: + The push did not contain any new commits, but force pushed to delete the commits and changes below. + +%h4 + = @reverse_compare ? "Deleted commits:" : "Commits:" %ul - @commits.each do |commit| @@ -9,22 +15,52 @@ %div %span by #{commit.author_name} %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} - %pre #{commit.safe_message} + %pre.commit-message + = commit.safe_message + +%h4 #{pluralize @diffs.count, "changed file"}: + +%ul + - @diffs.each_with_index do |diff, i| + %li.file-stats + %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" } + - if diff.deleted_file + %span.deleted-file + − + = diff.old_path + - elsif diff.renamed_file + = diff.old_path + → + = diff.new_path + - elsif diff.new_file + %span.new-file + + + = diff.new_path + - else + = diff.new_path -%h4 Changes: -- @diffs.each do |diff| - %li - %strong - - if diff.old_path == diff.new_path - = diff.new_path - - elsif diff.new_path && diff.old_path - #{diff.old_path} → #{diff.new_path} - - else - = diff.new_path || diff.old_path - %hr - %pre - = color_email_diff(diff.diff) - %br +- unless @disable_diffs + %h4 Changes: + - @diffs.each_with_index do |diff, i| + %li{id: "diff-#{i}"} + %a{href: @target_url + "#diff-#{i}"} + - if diff.deleted_file + %strong + = diff.old_path + deleted + - elsif diff.renamed_file + %strong + = diff.old_path + → + %strong + = diff.new_path + - else + %strong + = diff.new_path + %hr + %pre + = color_email_diff(diff.diff) + %br - if @compare.timeout %h5 Huge diff. To prevent performance issues changes are hidden diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml index fa355cb5269..8d67a42234e 100644 --- a/app/views/notify/repository_push_email.text.haml +++ b/app/views/notify/repository_push_email.text.haml @@ -1,25 +1,47 @@ -#{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} - +#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} \ -Commits: +\ +- if @reverse_compare + WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below. + \ + \ += @reverse_compare ? "Deleted commits:" : "Commits:" - @commits.each do |commit| - #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)} by #{commit.author_name} + #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} #{commit.safe_message} \- - - - - \ \ -Changes: +#{pluralize @diffs.count, "changed file"}: +\ - @diffs.each do |diff| - \ - \===================================== - - if diff.old_path == diff.new_path - = diff.new_path - - elsif diff.new_path && diff.old_path - #{diff.old_path} → #{diff.new_path} + - if diff.deleted_file + \- − #{diff.old_path} + - elsif diff.renamed_file + \- #{diff.old_path} → #{diff.new_path} + - elsif diff.new_file + \- + #{diff.new_path} - else - = diff.new_path || diff.old_path - \===================================== - != diff.diff -\ + \- #{diff.new_path} +- unless @disable_diffs + \ + \ + Changes: + - @diffs.each do |diff| + \ + \===================================== + - if diff.deleted_file + #{diff.old_path} deleted + - elsif diff.renamed_file + #{diff.old_path} → #{diff.new_path} + - else + = diff.new_path + \===================================== + != diff.diff - if @compare.timeout + \ + \ Huge diff. To prevent performance issues it was hidden +\ +\ +View it on GitLab: #{@target_url} diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index f124637c07b..6bafcb56551 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -57,7 +57,7 @@ %p.light = user_url(@user) %div - = f.submit 'Save username', class: "btn btn-save" + = f.submit 'Save username', class: "btn btn-warning" - if show_profile_remove_tab? %fieldset.remove-account diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml index 8d09595fd4f..cc00d08d03b 100644 --- a/app/views/profiles/design.html.haml +++ b/app/views/profiles/design.html.haml @@ -33,6 +33,11 @@ .prev.violet = f.radio_button :theme_id, 5 Violet + + = label_tag do + .prev.blue + = f.radio_button :theme_id, 6 + Blue %br .clearfix diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index e3cd323927e..6cf5c81c19e 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -50,7 +50,7 @@ %p You will receive all notifications from projects in which you participate .form-actions - = f.submit 'Save changes', class: "btn btn-save" + = f.submit 'Save changes', class: "btn btn-create" .clearfix %hr diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 3b1ebbfaf59..4b04b113e89 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -35,4 +35,4 @@ .col-sm-10 = f.password_field :password_confirmation, required: true, class: 'form-control' .form-actions - = f.submit 'Save password', class: "btn btn-save" + = f.submit 'Save password', class: "btn btn-create" diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index b2808c46c00..1a7bc353bf3 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -56,7 +56,7 @@ .form-group = f.label :bio, class: "control-label" .col-sm-10 - = f.text_area :bio, rows: 6, class: "form-control", maxlength: 250 + = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250 %span.help-block Tell us about yourself in fewer than 250 characters. .col-md-5 @@ -89,10 +89,12 @@ = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" - if @user.public_profile? - .bs-callout.bs-callout-info + .alert.alert-info %h4 Public profile %p Your profile is publicly visible because you joined public project(s) - .form-actions - = f.submit 'Save changes', class: "btn btn-save" + .row + .col-md-7 + .col-sm-2 + = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml index 5c52f91927d..07d4d602769 100644 --- a/app/views/projects/_bitbucket_import_modal.html.haml +++ b/app/views/projects/_bitbucket_import_modal.html.haml @@ -10,4 +10,4 @@ you need to - else your GitLab administrator needs to - == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/butbucket.md'}.
\ No newline at end of file + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md'}. diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index bfacab5e48e..a7cd129b631 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -50,6 +50,7 @@ = f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, { class: 'select2' }) - else + .prepend-top-10 %span.light No open milestones available. - if can? current_user, :admin_milestone, issuable.project @@ -63,6 +64,7 @@ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, { selected: issuable.label_ids }, multiple: true, class: 'select2' - else + .prepend-top-10 %span.light No labels yet. - if can? current_user, :admin_label, issuable.project diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 64cc3fad6cf..9ff61f3887f 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -15,7 +15,7 @@ - else = link_to title, '#' -%ul.blob-commit-info.bs-callout.bs-callout-info.hidden-xs +%ul.blob-commit-info.well.hidden-xs - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path) = render blob_commit, project: @project diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index 20e51d18da5..9b5eb84a86d 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -26,7 +26,7 @@ %a{href: "#diff-#{i}"} %i.fa.fa-minus = diff.old_path - \-> + → = diff.new_path - elsif diff.new_file %span.new-file diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index c9a6b3ebd9e..af1f342afbd 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -1,4 +1,4 @@ -.bs-callout.bs-callout-warning +.alert.alert-warning %h4 Too many changes. .pull-right diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 097374e1128..f1248ac2af5 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -12,7 +12,7 @@ %span Import existing git repo .col-sm-10 = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' - .bs-callout.bs-callout-info + .alert.alert-info This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. %br The import will time out after 4 minutes. For big repositories, use a clone/push combination. diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 7defc8787a9..2cb94d10b6f 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -6,14 +6,7 @@ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do %i.fa.fa-rss - = form_tag namespace_project_issues_path(@project.namespace, @project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do - .append-right-10.hidden-xs.hidden-sm - = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } - = hidden_field_tag :state, params['state'] - = hidden_field_tag :scope, params['scope'] - = hidden_field_tag :assignee_id, params['assignee_id'] - = hidden_field_tag :milestone_id, params['milestone_id'] - = hidden_field_tag :label_id, params['label_id'] + = render 'shared/issuable_search_form', path: namespace_project_issues_path(@project.namespace, @project) - if can? current_user, :write_issue, @project = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do @@ -25,11 +18,11 @@ .clearfix .issues_bulk_update.hide = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do - = select_tag('update[status]', options_for_select([['Open', 'open'], ['Closed', 'closed']]), prompt: "Status") + = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status") = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee') = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") = hidden_field_tag 'update[issues_ids]', [] - = hidden_field_tag :status, params[:status] + = hidden_field_tag :state_event, params[:state_event] = button_tag "Update issues", class: "btn update_selected_issues btn-save" .issues-holder diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 95912536e42..2305fce112e 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -2,7 +2,7 @@ -if @label.errors.any? .row .col-sm-10.col-sm-offset-2 - .bs-callout.bs-callout-danger + .alert.alert-danger - @label.errors.full_messages.each do |msg| %span= msg %br diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml new file mode 100644 index 00000000000..b8a0ca9a42f --- /dev/null +++ b/app/views/projects/merge_requests/_merge_requests.html.haml @@ -0,0 +1,13 @@ +.panel.panel-default + %ul.well-list.mr-list + = render @merge_requests + - if @merge_requests.blank? + %li + .nothing-here-block No merge requests to show + +- if @merge_requests.present? + .pull-right + %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter + + = paginate @merge_requests, theme: "gitlab" + diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 73eccfa556e..bf80afe8785 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -99,11 +99,11 @@ - if @diffs.present? = render "projects/diffs/diffs", diffs: @diffs, project: @project - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - .bs-callout.bs-callout-danger + .alert.alert-danger %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. %p To preserve performance the line changes are not shown. - else - .bs-callout.bs-callout-danger + .alert.alert-danger %h4 This comparison includes a huge diff. %p To preserve performance the line changes are not shown. diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index e3b9a28033b..d7992bdd19e 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,22 +1,11 @@ -.merge-requests-holder - .append-bottom-10 - .pull-right - - if can? current_user, :write_merge_request, @project - = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do - %i.fa.fa-plus - New Merge Request - = render 'shared/issuable_filter' - .panel.panel-default - %ul.well-list.mr-list - = render @merge_requests - - if @merge_requests.blank? - %li - .nothing-here-block No merge requests to show - - if @merge_requests.present? - .pull-right - %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter - - = paginate @merge_requests, theme: "gitlab" +.append-bottom-10 + .pull-right + = render 'shared/issuable_search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) -:javascript - $(merge_requestsPage); + - if can? current_user, :write_merge_request, @project + = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do + %i.fa.fa-plus + New Merge Request + = render 'shared/issuable_filter' +.merge-requests-holder + = render 'merge_requests' diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index cfef1d5e4cc..786b5f39063 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -3,7 +3,7 @@ - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else - .bs-callout.bs-callout-warning + .alert.alert-warning %h4 Changes view for this comparison is extremely large. %p diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index 46f2df1b183..42fbd0cd2ca 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -3,3 +3,6 @@ = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do %span.cgray ##{merge_request.iid} = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title + .pull-right.assignee-icon + - if merge_request.assignee + = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16" diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index d32b2ba271f..dcf56541db8 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -23,5 +23,4 @@ = pluralize milestone.merge_requests.count, 'Merge Request' %span.light #{milestone.percent_complete}% complete - .progress.progress-info - .progress-bar{style: "width: #{milestone.percent_complete}%;"} + = milestone_progress_bar(milestone) diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index fea96f37011..110d8967342 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -43,8 +43,7 @@ %span.light #{@milestone.percent_complete}% complete %span.pull-right= @milestone.expires_at - .progress.progress-info - .progress-bar{style: "width: #{@milestone.percent_complete}%;"} + = milestone_progress_bar(@milestone) %ul.nav.nav-tabs diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 025c4fd5506..00b912742b2 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -34,7 +34,7 @@ %span Import existing git repo .col-sm-10 = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' - .bs-callout.bs-callout-info + .alert.alert-info.prepend-top-10 This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. %br The import will time out after 4 minutes. For big repositories, use a clone/push combination. @@ -65,7 +65,7 @@ %i.fa.fa-bitbucket Import projects from Bitbucket = render 'bitbucket_import_modal' - + - unless request.host == 'gitlab.com' .project-import.form-group .col-sm-2 diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index dc20e96732e..cfe28084170 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -2,7 +2,7 @@ %p.light Keep stable branches secure and force developers to use Merge Requests %hr -.bs-callout.bs-callout-info +.alert.alert-info %p Protected branches are designed to %ul %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 55ac85c32b9..3492dd5babd 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -18,7 +18,7 @@ %li= msg - if @service.help.present? - .bs-callout + .alert.alert-info = preserve do = markdown @service.help @@ -47,6 +47,14 @@ %strong Tag push events %p.light This url will be triggered when a new tag is pushed to the repository + - if @service.supported_events.include?("note") + %div + = f.check_box :note_events, class: 'pull-left' + .prepend-left-20 + = f.label :note_events, class: 'list-label' do + %strong Comments + %p.light + This url will be triggered when someone adds a comment - if @service.supported_events.include?("issue") %div = f.check_box :issues_events, class: 'pull-left' @@ -66,14 +74,16 @@ - @service.fields.each do |field| - name = field[:name] - - value = @service.send(name) unless field[:type] == 'password' + - title = field[:title] || name.humanize + - value = service_field_value(field[:type], @service.send(name)) - type = field[:type] - placeholder = field[:placeholder] - choices = field[:choices] - default_choice = field[:default_choice] + - help = field[:help] .form-group - = f.label name, class: "control-label" + = f.label name, title, class: "control-label" .col-sm-10 - if type == 'text' = f.text_field name, class: "form-control", placeholder: placeholder @@ -84,7 +94,9 @@ - elsif type == 'select' = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } - elsif type == 'password' - = f.password_field name, class: 'form-control' + = f.password_field name, placeholder: value, class: 'form-control' + - if help + %span.help-block= help .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 787cfd9304f..74b07395650 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -40,7 +40,7 @@ %p Repository is read-only - if @project.forked_from_project - .alert.alert-success + .well %i.fa.fa-code-fork.project-fork-icon Forked from: %br diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml index 61c50af31bf..eb815447407 100644 --- a/app/views/projects/team_members/_team_member.html.haml +++ b/app/views/projects/team_members/_team_member.html.haml @@ -12,6 +12,7 @@ = image_tag avatar_icon(user.email, 32), class: "avatar s32" %p %strong= user.name + - if user.blocked? + %label.label.label-danger + %strong Blocked %span.cgray= user.username - - diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index 5875f71bac2..b34dd53e3b5 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -15,7 +15,7 @@ = f.text_field :path, placeholder: 'open-source', class: 'form-control', autofocus: local_assigns[:autofocus] || false - if @group.persisted? - .bs-callout.bs-callout-danger + .alert.alert-danger %ul %li Changing group path can have unintended side effects. %li Renaming group path will rename directory for all related projects diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/_issuable_search_form.html.haml new file mode 100644 index 00000000000..639d203dcd6 --- /dev/null +++ b/app/views/shared/_issuable_search_form.html.haml @@ -0,0 +1,9 @@ += form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do + .append-right-10.hidden-xs.hidden-sm + = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } + = hidden_field_tag :state, params['state'] + = hidden_field_tag :scope, params['scope'] + = hidden_field_tag :assignee_id, params['assignee_id'] + = hidden_field_tag :author_id, params['author_id'] + = hidden_field_tag :milestone_id, params['milestone_id'] + = hidden_field_tag :label_id, params['label_id'] diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index 1a2946baccb..089179e677a 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,8 +1,8 @@ - if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? .no-ssh-key-message.alert.alert-warning.hidden-xs - You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile + You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path, class: 'alert-link'} to your profile .pull-right - = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put + = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link' | - = link_to 'Remind later', '#', class: 'hide-no-ssh-message' + = link_to 'Remind later', '#', class: 'hide-no-ssh-message alert-link' diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml new file mode 100644 index 00000000000..8746970c239 --- /dev/null +++ b/app/views/shared/_project.html.haml @@ -0,0 +1,21 @@ += cache [project, controller.controller_name, controller.action_name] do + = link_to project_path(project), class: dom_class(project) do + - if avatar + .dash-project-avatar + = project_icon(project, alt: '', class: 'avatar project-avatar s40') + .dash-project-access-icon + = visibility_level_icon(project.visibility_level) + %span.str-truncated + %span.namespace-name + - if project.namespace + = project.namespace.human_name + \/ + %span.project-name.filter-title + = project.name + - if stars + %span.pull-right.light + %i.fa.fa-star + = project.star_count + - else + %span.arrow + %i.fa.fa-angle-right diff --git a/app/views/shared/_projects_list.html.haml b/app/views/shared/_projects_list.html.haml new file mode 100644 index 00000000000..4c58092af44 --- /dev/null +++ b/app/views/shared/_projects_list.html.haml @@ -0,0 +1,17 @@ +- projects_limit = 20 unless local_assigns[:projects_limit] +- avatar = true unless local_assigns[:avatar] == false +- stars = false unless local_assigns[:stars] == true +%ul.well-list.projects-list + - projects.each_with_index do |project, i| + %li{class: (i >= projects_limit) ? 'project-row hide' : 'project-row'} + = render "shared/project", project: project, avatar: avatar, stars: stars + - if projects.blank? + %li + .nothing-here-block There are no projects here. + - if projects.count > projects_limit + %li.bottom + %span.light + #{projects_limit} of #{pluralize(projects.count, 'project')} displayed. + %span + = link_to '#', class: 'js-expand' do + Show all diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index cb84570a6d5..f360fbb3d5d 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,4 +1,4 @@ .clearfix - groups.each do |group| = link_to group, class: 'profile-groups-avatars inline', title: group.name do - = image_tag group_icon(group.path), class: 'avatar group-avatar s40' + = image_tag group_icon(group), class: 'avatar group-avatar s40' diff --git a/app/views/users/_profile.html.haml b/app/views/users/_profile.html.haml index 3b44959baad..0a70b738071 100644 --- a/app/views/users/_profile.html.haml +++ b/app/views/users/_profile.html.haml @@ -12,7 +12,7 @@ - unless user.linkedin.blank? %li %span.light LinkedIn: - %strong= user.linkedin + %strong= link_to user.linkedin, "http://www.linkedin.com/in/#{user.linkedin}" - unless user.twitter.blank? %li %span.light Twitter: diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml index c925a48f550..6c7779be30e 100644 --- a/app/views/users/_projects.html.haml +++ b/app/views/users/_projects.html.haml @@ -1,21 +1,13 @@ - if @contributed_projects.present? .panel.panel-default .panel-heading Projects contributed to - %ul.well-list - - @contributed_projects.sort_by(&:star_count).reverse.each do |project| - %li - = link_to_project project - %span.pull-right.light - %i.fa.fa-star - = project.star_count + = render 'shared/projects_list', + projects: @contributed_projects.sort_by(&:star_count).reverse, + projects_limit: 5, stars: true, avatar: false - if @projects.present? .panel.panel-default .panel-heading Personal projects - %ul.well-list - - @projects.sort_by(&:star_count).reverse.each do |project| - %li - = link_to_project project - %span.pull-right.light - %i.fa.fa-star - = project.star_count + = render 'shared/projects_list', + projects: @projects.sort_by(&:star_count).reverse, + projects_limit: 10, stars: true, avatar: false diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 5e82d5780cf..abd6b229782 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,5 +1,7 @@ .row - .col-md-8 + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %section.col-md-8 %h3.page-title = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' = @user.name @@ -19,10 +21,11 @@ = render 'groups', groups: @groups %hr - .user-calendar - %h4.center.light - %i.fa.fa-spinner.fa-spin - %hr + .hidden-xs + .user-calendar + %h4.center.light + %i.fa.fa-spinner.fa-spin + %hr %h4 User Activity @@ -33,7 +36,7 @@ %i.fa.fa-rss = render @events - .col-md-4 + %aside.col-md-4 = render 'profile', user: @user = render 'projects' diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index e3f6f3a6aef..e59ca81defe 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -1,25 +1,41 @@ class EmailsOnPushWorker include Sidekiq::Worker - def perform(project_id, recipients, push_data) + def perform(project_id, recipients, push_data, send_from_committer_email = false, disable_diffs = false) project = Project.find(project_id) before_sha = push_data["before"] after_sha = push_data["after"] branch = push_data["ref"] author_id = push_data["user_id"] - if before_sha =~ /^000000/ || after_sha =~ /^000000/ + if Gitlab::Git.blank_ref?(before_sha) || Gitlab::Git.blank_ref?(after_sha) # skip if new branch was pushed or branch was removed return true end compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) - # Do not send emails if git compare failed - return false unless compare && compare.commits.present? + return false if compare.same + + if compare.commits.empty? + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) + + reverse_compare = true + + return false if compare.commits.empty? + end recipients.split(" ").each do |recipient| - Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver + Notify.repository_push_email( + project_id, + recipient, + author_id, + branch, + compare, + reverse_compare, + send_from_committer_email, + disable_diffs + ).deliver end ensure compare = nil diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 613bae351d8..e1a99d9cad8 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -57,9 +57,9 @@ class IrkerWorker end def send_branch_updates(push_data, project, repo_name, committer, branch) - if push_data['before'] =~ /^000000/ + if push_data['before'] == Gitlab::Git::BLANK_SHA send_new_branch project, repo_name, committer, branch - elsif push_data['after'] =~ /^000000/ + elsif push_data['after'] == Gitlab::Git::BLANK_SHA send_del_branch repo_name, committer, branch end end @@ -83,7 +83,7 @@ class IrkerWorker return if push_data['total_commits_count'] == 0 # Next message is for number of commit pushed, if any - if push_data['before'] =~ /^000000/ + if push_data['before'] == Gitlab::Git::BLANK_SHA # Tweak on push_data["before"] in order to have a nice compare URL push_data['before'] = before_on_new_branch push_data, project end diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 1406cba2db3..ecc6c8e53a3 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -33,7 +33,7 @@ class PostReceive return false end - if tag?(ref) + if Gitlab::Git.tag_ref?(ref) GitTagPushService.new.execute(project, @user, oldrev, newrev, ref) else GitPushService.new.execute(project, @user, oldrev, newrev, ref) @@ -44,10 +44,4 @@ class PostReceive def log(message) Gitlab::GitLogger.error("POST-RECEIVE: #{message}") end - - private - - def tag?(ref) - !!(/refs\/tags\/(.*)/.match(ref)) - end end diff --git a/config/application.rb b/config/application.rb index bd4578848c5..fa399533e52 100644 --- a/config/application.rb +++ b/config/application.rb @@ -50,6 +50,8 @@ module Gitlab # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' + config.action_view.sanitized_allowed_protocols = %w(smb) + # Relative url support # Uncomment and customize the last line to run in a non-root path # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 6dff07cf9df..75d9e65aefe 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -43,6 +43,7 @@ production: &base # email_enabled: true # Email address used in the "From" field in mails sent by GitLab email_from: example@example.com + email_display_name: GitLab # Email server smtp settings are in config/initializers/smtp_settings.rb.sample diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 6a8bbb80b9c..70af7a829c4 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -102,6 +102,7 @@ Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" +Settings.gitlab['email_display_name'] ||= "GitLab" Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' Settings.gitlab['user_home'] ||= begin diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample index e00923e7e0c..f0fe2fdfa43 100644 --- a/config/initializers/smtp_settings.rb.sample +++ b/config/initializers/smtp_settings.rb.sample @@ -3,6 +3,9 @@ # 2. Edit settings inside this file # 3. Restart GitLab instance # +# For full list of options and their values see http://api.rubyonrails.org/classes/ActionMailer/Base.html +# + if Rails.env.production? Gitlab::Application.config.action_mailer.delivery_method = :smtp @@ -14,6 +17,6 @@ if Rails.env.production? domain: "gitlab.company.com", authentication: :login, enable_starttls_auto: true, - openssl_verify_mode: 'none' + openssl_verify_mode: 'peer' # See ActionMailer documentation for other possible options } end diff --git a/config/initializers/timeout.rb b/config/initializers/timeout.rb new file mode 100644 index 00000000000..bc88595cf26 --- /dev/null +++ b/config/initializers/timeout.rb @@ -0,0 +1,8 @@ +# Slowpoke extends Rack::Timeout to gracefully kill Unicorn workers so they can clean up state. +Slowpoke.timeout = 60 + +# The `Rack::Timeout` middleware kills requests after 60 seconds (as set above). +# We're replacing it with our `Gitlab::Middleware::Timeout` that does the same, +# except ignoring Git-over-HTTP requests, letting those take as long as they need. + +Rails.application.config.middleware.swap(Rack::Timeout, Gitlab::Middleware::Timeout) diff --git a/config/routes.rb b/config/routes.rb index 5348c86ea9d..889995e92a6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,9 +7,8 @@ Gitlab::Application.routes.draw do authorized_applications: 'oauth/authorized_applications', authorizations: 'oauth/authorizations' end - # + # Search - # get 'search' => 'search#show' get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete @@ -33,13 +32,11 @@ Gitlab::Application.routes.draw do receive_pack: Gitlab.config.gitlab_shell.receive_pack }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] - # # Help - # - get 'help' => 'help#index' get 'help/:category/:file' => 'help#show', as: :help_page get 'help/shortcuts' + get 'help/ui' => 'help#ui' # # Global snippets @@ -73,7 +70,7 @@ Gitlab::Application.routes.draw do get :callback get :jobs end - + resource :gitorious, only: [:create, :new], controller: :gitorious do get :status get :callback @@ -196,11 +193,6 @@ Gitlab::Application.routes.draw do end resources :keys resources :emails, only: [:index, :create, :destroy] - resources :groups, only: [:index] do - member do - delete :leave - end - end resource :avatar, only: [:destroy] end end @@ -216,13 +208,24 @@ Gitlab::Application.routes.draw do # resource :dashboard, controller: 'dashboard', only: [:show] do member do - get :projects get :issues get :merge_requests end scope module: :dashboard do resources :milestones, only: [:index, :show] + + resources :groups, only: [:index] do + member do + delete :leave + end + end + + resources :projects, only: [] do + collection do + get :starred + end + end end end diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index d8b4f5c7c32..29253b71f49 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -35,22 +35,10 @@ working_directory "/home/git/gitlab" # available in 0.94.0+ listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 1024 listen "127.0.0.1:8080", :tcp_nopush => true -# nuke workers after 30 seconds instead of 60 seconds (the default) -# -# NOTICE: git push over http depends on this value. -# If you want be able to push huge amount of data to git repository over http -# you will have to increase this value too. -# -# Example of output if you try to push 1GB repo to GitLab over http. -# -> git push http://gitlab.... master -# -# error: RPC failed; result=18, HTTP code = 200 -# fatal: The remote end hung up unexpectedly -# fatal: The remote end hung up unexpectedly -# -# For more information see http://stackoverflow.com/a/21682112/752049 -# -timeout 60 +# Kill workers after 1 hour. +# A shorter timeout of 60 seconds is enforced by rack-timeout for web requests. +# Git-over-HTTP only has the below timeout since large pulls/pushes can take a long time. +timeout 60 * 60 # feel free to point this anywhere accessible on the filesystem pid "/home/git/gitlab/tmp/pids/unicorn.pid" diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb index b697f58d4ef..24952a1f661 100644 --- a/db/fixtures/development/05_users.rb +++ b/db/fixtures/development/05_users.rb @@ -1,30 +1,31 @@ Gitlab::Seeder.quiet do (2..20).each do |i| begin - User.seed(:id, [{ - id: i, + User.create!( username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email, - confirmed_at: DateTime.now - }]) + confirmed_at: DateTime.now, + password: '12345678' + ) + print '.' - rescue ActiveRecord::RecordNotSaved + rescue ActiveRecord::RecordInvalid print 'F' end end (1..5).each do |i| begin - User.seed do |s| - s.username = "user#{i}" - s.name = "User #{i}" - s.email = "user#{i}@example.com" - s.confirmed_at = DateTime.now - s.password = '12345678' - end + User.create!( + username: "user#{i}", + name: "User #{i}", + email: "user#{i}@example.com", + confirmed_at: DateTime.now, + password: '12345678' + ) print '.' - rescue ActiveRecord::RecordNotSaved + rescue ActiveRecord::RecordInvalid print 'F' end end diff --git a/db/migrate/20150225065047_add_note_events_to_services.rb b/db/migrate/20150225065047_add_note_events_to_services.rb new file mode 100644 index 00000000000..d54ba9e482f --- /dev/null +++ b/db/migrate/20150225065047_add_note_events_to_services.rb @@ -0,0 +1,5 @@ +class AddNoteEventsToServices < ActiveRecord::Migration + def change + add_column :services, :note_events, :boolean, default: true, null: false + end +end diff --git a/db/migrate/20150306023106_fix_namespace_duplication.rb b/db/migrate/20150306023106_fix_namespace_duplication.rb new file mode 100644 index 00000000000..334e5574559 --- /dev/null +++ b/db/migrate/20150306023106_fix_namespace_duplication.rb @@ -0,0 +1,21 @@ +class FixNamespaceDuplication < ActiveRecord::Migration + def up + #fixes path duplication + select_all('SELECT MAX(id) max, COUNT(id) cnt, path FROM namespaces GROUP BY path HAVING COUNT(id) > 1').each do |nms| + bad_nms_ids = select_all("SELECT id FROM namespaces WHERE path = '#{nms['path']}' AND id <> #{nms['max']}").map{|x| x["id"]} + execute("UPDATE projects SET namespace_id = #{nms["max"]} WHERE namespace_id IN(#{bad_nms_ids.join(', ')})") + execute("DELETE FROM namespaces WHERE id IN(#{bad_nms_ids.join(', ')})") + end + + #fixes name duplication + select_all('SELECT MAX(id) max, COUNT(id) cnt, name FROM namespaces GROUP BY name HAVING COUNT(id) > 1').each do |nms| + bad_nms_ids = select_all("SELECT id FROM namespaces WHERE name = '#{nms['name']}' AND id <> #{nms['max']}").map{|x| x["id"]} + execute("UPDATE projects SET namespace_id = #{nms["max"]} WHERE namespace_id IN(#{bad_nms_ids.join(', ')})") + execute("DELETE FROM namespaces WHERE id IN(#{bad_nms_ids.join(', ')})") + end + end + + def down + # not implemented + end +end diff --git a/db/migrate/20150306023112_add_unique_index_to_namespace.rb b/db/migrate/20150306023112_add_unique_index_to_namespace.rb new file mode 100644 index 00000000000..6472138e3ef --- /dev/null +++ b/db/migrate/20150306023112_add_unique_index_to_namespace.rb @@ -0,0 +1,9 @@ +class AddUniqueIndexToNamespace < ActiveRecord::Migration + def change + remove_index :namespaces, column: :name if index_exists?(:namespaces, :name) + remove_index :namespaces, column: :path if index_exists?(:namespaces, :path) + + add_index :namespaces, :name, unique: true + add_index :namespaces, :path, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 1a9b512e159..3afbc082b70 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: 20150223022001) do +ActiveRecord::Schema.define(version: 20150306023112) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -242,9 +242,9 @@ ActiveRecord::Schema.define(version: 20150223022001) do end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree - add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree + add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree - add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree + add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: true do |t| @@ -334,12 +334,12 @@ ActiveRecord::Schema.define(version: 20150223022001) do t.string "import_url" t.integer "visibility_level", default: 0, null: false t.boolean "archived", default: false, null: false - t.string "avatar" t.string "import_status" t.float "repository_size", default: 0.0 t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" + t.string "avatar" end add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree @@ -371,6 +371,7 @@ ActiveRecord::Schema.define(version: 20150223022001) do t.boolean "issues_events", default: true t.boolean "merge_requests_events", default: true t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false end add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree diff --git a/doc/development/README.md b/doc/development/README.md index c31e5d7ae97..d5d264be19d 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -5,3 +5,4 @@ - [Rake tasks](rake_tasks.md) for development - [CI setup](ci_setup.md) for testing GitLab - [Sidekiq debugging](sidekiq_debugging.md) +- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md new file mode 100644 index 00000000000..2f01defc11d --- /dev/null +++ b/doc/development/ui_guide.md @@ -0,0 +1,12 @@ +# UI Guide for building GitLab + +## Best practices for creating new pages in GitLab + +TODO: write some best practices when develop GitLab features. + +## GitLab UI development kit + +We created a page inside GitLab where you can check commonly used html and css elements. + +When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples +you can use during GitLab development. diff --git a/doc/integration/github.md b/doc/integration/github.md index a9f1bc31bb4..b64501c2aaa 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -53,7 +53,7 @@ GitHub will generate an application ID and secret key for you to use. "app_id" => "YOUR_APP_ID", "app_secret" => "YOUR_APP_SECRET", "url" => "https://github.com/", - "args" => { "scope" => "user:email" } } + "args" => { "scope" => "user:email" } } ] ``` @@ -76,4 +76,4 @@ GitHub will generate an application ID and secret key for you to use. On the sign in page there should now be a GitHub icon below the regular sign in form. Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. -If everything goes well the user will be returned to GitLab and will be signed in.
\ No newline at end of file +If everything goes well the user will be returned to GitLab and will be signed in. diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md index 49ffaa62af8..216f1f11a9b 100644 --- a/doc/integration/gitlab.md +++ b/doc/integration/gitlab.md @@ -58,7 +58,7 @@ GitLab.com will generate an application ID and secret key for you to use. "name" => "gitlab", "app_id" => "YOUR_APP_ID", "app_secret" => "YOUR_APP_SECRET", - "args" => { "scope" => "api" } } + "args" => { "scope" => "api" } } ] ``` @@ -81,4 +81,4 @@ GitLab.com will generate an application ID and secret key for you to use. On the sign in page there should now be a GitLab.com icon below the regular sign in form. Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application. -If everything goes well the user will be returned to your GitLab instance and will be signed in.
\ No newline at end of file +If everything goes well the user will be returned to your GitLab instance and will be signed in. diff --git a/doc/integration/google.md b/doc/integration/google.md index d7b741ece69..e1c14c7c948 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -55,7 +55,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application "name" => "google_oauth2", "app_id" => "YOUR_APP_ID", "app_secret" => "YOUR_APP_SECRET", - "args" => { "access_type" => "offline", "approval_prompt" => '' } } + "args" => { "access_type" => "offline", "approval_prompt" => '' } } ] ``` diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 1096ea9656c..52779103775 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -140,25 +140,25 @@ But let's throw in a <b>tag</b>. ## Emoji - Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you: + Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: - :high_voltage_sign: You can use emoji anywhere GFM is supported. :victory_hand: + :zap: You can use emoji anywhere GFM is supported. :v: - You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches. And if someone improves your really :snail: code, send them some :cake:. People will :heart: you for that. + You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. + If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. - Consult the [Emoji Cheat Sheet](https://s3.amazonaws.com/emoji-cheatsheet/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup: + Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: -Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you: +Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: -:high_voltage_sign: You can use emoji anywhere GFM is supported. :victory_hand: +:zap: You can use emoji anywhere GFM is supported. :v: -You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches. And if someone improves your really :snail: code, send them some :cake:. People will :heart: you for that. +You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. -If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. +If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. -Consult the [Emoji Cheat Sheet](https://s3.amazonaws.com/emoji-cheatsheet/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup: +Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: ## Special GitLab References diff --git a/doc/release/monthly.md b/doc/release/monthly.md index dd44c1eb860..ec96be27f3f 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -51,6 +51,7 @@ Xth: (5 working days before the 22nd) Xth: (4 working days before the 22nd) - [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) +- [ ] Update ci.gitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) - [ ] Create regression issues (CE, CI) (#LINK) - [ ] Tweet about rc1 (#LINK) @@ -68,6 +69,7 @@ Xth: (1 working day before the 22nd) - [ ] Create CE, EE, CI stable versions (#LINK) - [ ] Create Omnibus tags and build packages - [ ] Update GitLab.com with the stable version (#LINK) +- [ ] Update ci.gitLab.com with the stable version (#LINK) 22nd: @@ -200,3 +202,7 @@ Consider creating a post on Hacker News. ## Update GitLab.com with the stable version - Deploy the package (should not need downtime because of the small difference with RC1) + +## Release new AMIs + +[Follow this guide](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md) diff --git a/doc/release/patch.md b/doc/release/patch.md index 80afa19b6c5..68156ae9c0e 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -51,6 +51,8 @@ CE=false be rake release['x.x.x'] 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) 1. Apply the patch to GitLab.com and the private GitLab development server +1. Apply the patch to ci.gitLab.com and the private GitLab CI development server 1. Create and publish a blog post, see [patch release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/patch_release_blog_template.md) 1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) +1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md) diff --git a/doc/release/security.md b/doc/release/security.md index b67e0f37a04..60bcfbb6da5 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -18,11 +18,13 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c 1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server" 1. The MR with the security fix should get a 'security' label and be assigned to the release manager 1. Build the package for GitLab.com and do a deploy +1. Build the package for ci.gitLab.com and do a deploy +1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md) 1. Create feature branches for the blog post on GitLab.com and link them from the code branch 1. Merge and publish the blog posts 1. Send tweets about the release from `@gitlabhq` 1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq) -1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number +1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number. CVE is only needed for bugs that allow someone to own the server (Remote Code Execution) or access to code of projects they are not a member of. 1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/) 1. Thank the security researcher in an email for their cooperation 1. Update the blog post and the CHANGELOG when we receive the CVE number diff --git a/docker/Dockerfile b/docker/Dockerfile index 3584a754c62..4eb280f9554 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update -q \ # If the Omnibus package version below is outdated please contribute a merge request to update it. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it. RUN TMP_FILE=$(mktemp); \ - wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.1-omnibus-1_amd64.deb \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.3-omnibus-1_amd64.deb \ && dpkg -i $TMP_FILE \ && rm -f $TMP_FILE diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature index 3af93bc373c..69b3a776441 100644 --- a/features/dashboard/archived_projects.feature +++ b/features/dashboard/archived_projects.feature @@ -10,8 +10,3 @@ Feature: Dashboard Archived Projects Scenario: I should see non-archived projects on dashboard Then I should see "Shop" project link And I should not see "Forum" project link - - Scenario: I should see all projects on projects page - And I visit dashboard projects page - Then I should see "Shop" project link - And I should see "Forum" project link diff --git a/features/profile/group.feature b/features/dashboard/group.feature index e2fbfde77be..cf4b8d7283b 100644 --- a/features/profile/group.feature +++ b/features/dashboard/group.feature @@ -1,5 +1,5 @@ -@profile -Feature: Profile Group +@dashboard +Feature: Dashboard Group Background: Given I sign in as "John Doe" And "John Doe" is owner of group "Owned" @@ -10,18 +10,18 @@ Feature: Profile Group @javascript Scenario: Owner should be able to leave from group if he is not the last owner Given "Mary Jane" is owner of group "Owned" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list When I click on the "Leave" button for group "Owned" - And I visit profile groups page + And I visit dashboard groups page Then I should not see group "Owned" in group list Then I should see group "Guest" in group list @javascript Scenario: Owner should not be able to leave from group if he is the last owner Given "Mary Jane" is guest of group "Owned" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list Then I should not see the "Leave" button for group "Owned" @@ -29,20 +29,28 @@ Feature: Profile Group @javascript Scenario: Guest should be able to leave from group Given "Mary Jane" is guest of group "Guest" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list When I click on the "Leave" button for group "Guest" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should not see group "Guest" in group list @javascript Scenario: Guest should be able to leave from group even if he is the only user in the group - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list When I click on the "Leave" button for group "Guest" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should not see group "Guest" in group list + + Scenario: Create a group from dasboard + And I visit dashboard groups page + And I click new group link + And submit form with new group "Samurai" info + Then I should be redirected to group "Samurai" page + And I should see newly created group "Samurai" + diff --git a/features/dashboard/projects.feature b/features/dashboard/projects.feature deleted file mode 100644 index bb4e84f0159..00000000000 --- a/features/dashboard/projects.feature +++ /dev/null @@ -1,9 +0,0 @@ -@dashboard -Feature: Dashboard Projects - Background: - Given I sign in as a user - And I own project "Shop" - And I visit dashboard projects page - - Scenario: I should see projects list - Then I should see projects list diff --git a/features/dashboard/starred_projects.feature b/features/dashboard/starred_projects.feature new file mode 100644 index 00000000000..9dfd2fbab9c --- /dev/null +++ b/features/dashboard/starred_projects.feature @@ -0,0 +1,12 @@ +@dashboard +Feature: Dashboard Starred Projects + Background: + Given I sign in as a user + And public project "Community" + And I starred project "Community" + And I own project "Shop" + And I visit dashboard starred projects page + + Scenario: I should see projects list + Then I should see project "Community" + And I should not see project "Shop" diff --git a/features/groups.feature b/features/groups.feature index b5ff03db844..05546e0d6ef 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -10,14 +10,6 @@ Feature: Groups Then I should see group "Owned" projects list And I should see projects activity feed - Scenario: Create a group from dasboard - When I visit group "Owned" page - And I visit dashboard page - And I click new group link - And submit form with new group "Samurai" info - Then I should be redirected to group "Samurai" page - And I should see newly created group "Samurai" - Scenario: I should see group "Owned" issues list Given project from group "Owned" has issues assigned to me When I visit group "Owned" issues page diff --git a/features/project/archived.feature b/features/project/archived.feature index 9aac29384ba..ad466f4f307 100644 --- a/features/project/archived.feature +++ b/features/project/archived.feature @@ -14,15 +14,6 @@ Feature: Project Archived And I visit project "Forum" page Then I should see "Archived" - Scenario: I should not see archived on projects page with no archived projects - And I visit dashboard projects page - Then I should not see "Archived" - - Scenario: I should see archived on projects page with archived projects - And project "Forum" is archived - And I visit dashboard projects page - Then I should see "Archived" - Scenario: I archive project When project "Shop" has push event And I visit project "Shop" page diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 7c029f05d75..adad100e56c 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -218,3 +218,10 @@ Feature: Project Merge Requests And I click link "Edit" for the merge request And I preview a description text like "Bug fixed :smile:" Then I should see the Markdown write tab + + @javascript + Scenario: I search merge request + Given I click link "All" + When I fill in merge request search with "Fe" + Then I should see "Feature NS-03" in merge requests + And I should not see "Bug NS-04" in merge requests diff --git a/features/steps/profile/group.rb b/features/steps/dashboard/group.rb index 0a10e04e219..8384df2fb59 100644 --- a/features/steps/profile/group.rb +++ b/features/steps/dashboard/group.rb @@ -1,4 +1,4 @@ -class Spinach::Features::ProfileGroup < Spinach::FeatureSteps +class Spinach::Features::DashboardGroup < Spinach::FeatureSteps include SharedAuthentication include SharedGroup include SharedPaths @@ -41,4 +41,23 @@ class Spinach::Features::ProfileGroup < Spinach::FeatureSteps step 'I should not see group "Guest" in group list' do page.should_not have_content("Guest") end + + step 'I click new group link' do + click_link "New Group" + end + + step 'submit form with new group "Samurai" info' do + fill_in 'group_path', with: 'Samurai' + fill_in 'group_description', with: 'Tokugawa Shogunate' + click_button "Create group" + end + + step 'I should be redirected to group "Samurai" page' do + current_path.should == group_path(Group.find_by(name: 'Samurai')) + end + + step 'I should see newly created group "Samurai"' do + page.should have_content "Samurai" + page.should have_content "Tokugawa Shogunate" + end end diff --git a/features/steps/dashboard/projects.rb b/features/steps/dashboard/projects.rb deleted file mode 100644 index 2a348163060..00000000000 --- a/features/steps/dashboard/projects.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Spinach::Features::DashboardProjects < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - - step 'I should see projects list' do - @user.authorized_projects.all.each do |project| - page.should have_link project.name_with_namespace - end - end -end diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb new file mode 100644 index 00000000000..b9ad2f13e29 --- /dev/null +++ b/features/steps/dashboard/starred_projects.rb @@ -0,0 +1,15 @@ +class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + step 'I starred project "Community"' do + current_user.toggle_star(Project.find_by(name: 'Community')) + end + + step 'I should not see project "Shop"' do + within 'aside' do + page.should_not have_content('Shop') + end + end +end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index c3c34070e2e..91921f5e21c 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -72,25 +72,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps author: current_user end - When 'I click new group link' do - click_link "New group" - end - - step 'submit form with new group "Samurai" info' do - fill_in 'group_path', with: 'Samurai' - fill_in 'group_description', with: 'Tokugawa Shogunate' - click_button "Create group" - end - - step 'I should be redirected to group "Samurai" page' do - current_path.should == group_path(Group.find_by(name: 'Samurai')) - end - - step 'I should see newly created group "Samurai"' do - page.should have_content "Samurai" - page.should have_content "Tokugawa Shogunate" - end - step 'I change group "Owned" name to "new-name"' do fill_in 'group_name', with: 'new-name' fill_in 'group_path', with: 'new-name' diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 263f2ef2438..b67b2e58caf 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -276,6 +276,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end end + step 'I fill in merge request search with "Fe"' do + fill_in 'issue_search', with: "Fe" + end + def merge_request @merge_request ||= MergeRequest.find_by!(title: "Bug NS-05") end diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb index c229864bc83..9beb688bd16 100644 --- a/features/steps/shared/active_tab.rb +++ b/features/steps/shared/active_tab.rb @@ -26,7 +26,7 @@ module SharedActiveTab end step 'the active main tab should be Home' do - ensure_active_main_tab('Activity') + ensure_active_main_tab('Your Projects') end step 'the active main tab should be Projects' do diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 835b644e6c7..bb6c336d7cd 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -87,6 +87,18 @@ module SharedPaths visit help_path end + step 'I visit dashboard groups page' do + visit dashboard_groups_path + end + + step 'I should be redirected to the dashboard groups page' do + current_path.should == dashboard_groups_path + end + + step 'I visit dashboard starred projects page' do + visit starred_dashboard_projects_path + end + # ---------------------------------------- # Profile # ---------------------------------------- @@ -119,14 +131,6 @@ module SharedPaths visit history_profile_path end - step 'I visit profile groups page' do - visit profile_groups_path - end - - step 'I should be redirected to the profile groups page' do - current_path.should == profile_groups_path - end - # ---------------------------------------- # Admin # ---------------------------------------- diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 228a719fbdf..ee678d84c84 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -83,7 +83,10 @@ module API end def authenticate_by_gitlab_shell_token! - unauthorized! unless secret_token == params['secret_token'].try(:chomp) + input = params['secret_token'].try(:chomp) + unless Devise.secure_compare(secret_token, input) + unauthorized! + end end def authenticated_as_admin! diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 4a712c6345f..0c350d7c675 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -1,9 +1,25 @@ module Gitlab module Git BLANK_SHA = '0' * 40 + TAG_REF_PREFIX = "refs/tags/" + BRANCH_REF_PREFIX = "refs/heads/" - def self.extract_ref_name(ref) - ref.gsub(/\Arefs\/(tags|heads)\//, '') + class << self + def ref_name(ref) + ref.gsub(/\Arefs\/(tags|heads)\//, '') + end + + def tag_ref?(ref) + ref.start_with?(TAG_REF_PREFIX) + end + + def branch_ref?(ref) + ref.start_with?(BRANCH_REF_PREFIX) + end + + def blank_ref?(ref) + ref == BLANK_SHA + end end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 9b31190a882..cb69e4b13d3 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -115,7 +115,7 @@ module Gitlab # we dont allow force push to protected branch if forced_push?(project, oldrev, newrev) :force_push_code_to_protected_branches - elsif newrev == Gitlab::Git::BLANK_SHA + elsif Gitlab::Git.blank_ref?(newrev) # and we dont allow remove of protected branch :remove_protected_branches elsif project.developers_can_push_to_protected_branch?(branch_name) @@ -135,8 +135,8 @@ module Gitlab def branch_name(ref) ref = ref.to_s - if ref.start_with?('refs/heads') - ref.sub(%r{\Arefs/heads/}, '') + if Gitlab::Git.branch_ref?(ref) + Gitlab::Git.ref_name(ref) else nil end @@ -144,8 +144,8 @@ module Gitlab def tag_name(ref) ref = ref.to_s - if ref.start_with?('refs/tags') - ref.sub(%r{\Arefs/tags/}, '') + if Gitlab::Git.tag_ref?(ref) + Gitlab::Git.ref_name(ref) else nil end diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index 0c85acf7e69..6e30724e1f7 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -34,7 +34,14 @@ module Gitlab def allowed? if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter) return true unless ldap_config.active_directory - !Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) + + # Block user in GitLab if he/she was blocked in AD + if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) + user.block unless user.blocked? + false + else + true + end else false end diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb index 8af2c74e959..649cf3194b8 100644 --- a/lib/gitlab/ldap/authentication.rb +++ b/lib/gitlab/ldap/authentication.rb @@ -50,7 +50,7 @@ module Gitlab end def user_filter(login) - filter = Net::LDAP::Filter.eq(config.uid, login) + filter = Net::LDAP::Filter.equals(config.uid, login) # Apply LDAP user filter if present if config.user_filter.present? diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb index 3e0b3e6cbf8..3c426179375 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -9,10 +9,12 @@ module Gitlab attr_accessor :entry, :provider def self.find_by_uid(uid, adapter) + uid = Net::LDAP::Filter.escape(uid) adapter.user(adapter.config.uid, uid) end def self.find_by_dn(dn, adapter) + dn = Net::LDAP::Filter.escape(dn) adapter.user('dn', dn) end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index d85c2ee4f2d..2dfa18da482 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -14,6 +14,7 @@ module Gitlab # * !123 for merge requests # * $123 for snippets # * 123456 for commits + # * 123456...7890123 for commit ranges (comparisons) # # It also parses Emoji codes to insert images. See # http://www.emoji-cheat-sheet.com/ for a list of the supported icons. @@ -133,13 +134,14 @@ module Gitlab |#{PROJ_STR}?\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID |#{PROJ_STR}?!(?<merge_request>\d+) # MR ID |\$(?<snippet>\d+) # Snippet ID + |(#{PROJ_STR}@)?(?<commit_range>[\h]{6,40}\.{2,3}[\h]{6,40}) # Commit range |(#{PROJ_STR}@)?(?<commit>[\h]{6,40}) # Commit ID |(?<skip>gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit ) (?<suffix>\W)? # Suffix }x.freeze - TYPES = [:user, :issue, :label, :merge_request, :snippet, :commit].freeze + TYPES = [:user, :issue, :label, :merge_request, :snippet, :commit, :commit_range].freeze def parse_references(text, project = @project) # parse reference links @@ -290,6 +292,30 @@ module Gitlab end end + def reference_commit_range(identifier, project = @project, prefix_text = nil) + from_id, to_id = identifier.split(/\.{2,3}/, 2) + + inclusive = identifier !~ /\.{3}/ + from_id << "^" if inclusive + + if project.valid_repo? && + from = project.repository.commit(from_id) && + to = project.repository.commit(to_id) + + options = html_options.merge( + title: "Commits #{from_id} through #{to_id}", + class: "gfm gfm-commit_range #{html_options[:class]}" + ) + prefix_text = "#{prefix_text}@" if prefix_text + + link_to( + "#{prefix_text}#{identifier}", + namespace_project_compare_url(project.namespace, project, from: from_id, to: to_id), + options + ) + end + end + def reference_external_issue(identifier, project = @project, prefix_text = nil) url = url_for_issue(identifier, project) diff --git a/lib/gitlab/middleware/timeout.rb b/lib/gitlab/middleware/timeout.rb new file mode 100644 index 00000000000..015600392b9 --- /dev/null +++ b/lib/gitlab/middleware/timeout.rb @@ -0,0 +1,13 @@ +module Gitlab + module Middleware + class Timeout < Rack::Timeout + GRACK_REGEX = /[-\/\w\.]+\.git\//.freeze + + def call(env) + return @app.call(env) if env['PATH_INFO'] =~ GRACK_REGEX + + super + end + end + end +end diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb new file mode 100644 index 00000000000..644dec45dca --- /dev/null +++ b/lib/gitlab/note_data_builder.rb @@ -0,0 +1,77 @@ +module Gitlab + class NoteDataBuilder + class << self + # Produce a hash of post-receive data + # + # For all notes: + # + # data = { + # object_kind: "note", + # user: { + # name: String, + # username: String, + # avatar_url: String + # } + # project_id: Integer, + # repository: { + # name: String, + # url: String, + # description: String, + # homepage: String, + # } + # object_attributes: { + # <hook data for note> + # } + # <note-specific data>: { + # } + # note-specific data is a hash with one of the following keys and contains + # the hook data for that type. + # - commit + # - issue + # - merge_request + # - snippet + # + def build(note, user) + project = note.project + data = build_base_data(project, user, note) + + if note.for_commit? + data[:commit] = build_data_for_commit(project, user, note) + elsif note.for_issue? + data[:issue] = note.noteable.hook_attrs + elsif note.for_merge_request? + data[:merge_request] = note.noteable.hook_attrs + elsif note.for_project_snippet? + data[:snippet] = note.noteable.hook_attrs + end + + data + end + + def build_base_data(project, user, note) + base_data = { + object_kind: "note", + user: user.hook_attrs, + project_id: project.id, + repository: { + name: project.name, + url: project.url_to_repo, + description: project.description, + homepage: project.web_url, + }, + object_attributes: note.hook_attrs + } + + base_data[:object_attributes][:url] = + Gitlab::UrlBuilder.new(:note).build(note.id) + base_data + end + + def build_data_for_commit(project, user, note) + # commit_id is the SHA hash + commit = project.repository.commit(note.commit_id) + commit.hook_attrs(project) + end + end + end +end diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb index 5cefa67d3ab..0cc6b0ac694 100644 --- a/lib/gitlab/push_data_builder.rb +++ b/lib/gitlab/push_data_builder.rb @@ -58,6 +58,7 @@ module Gitlab data[:commits] << commit.hook_attrs(project) end + data[:commits] = "" if data[:commits].count == 0 data end @@ -65,12 +66,13 @@ module Gitlab # existing project and commits to test web hooks def build_sample(project, user) commits = project.repository.commits(project.default_branch, nil, 3) - build(project, user, commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", commits) + ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" + build(project, user, commits.last.id, commits.first.id, ref, commits) end def checkout_sha(repository, newrev, ref) - if newrev != Gitlab::Git::BLANK_SHA && ref.start_with?('refs/tags/') - tag_name = Gitlab::Git.extract_ref_name(ref) + if newrev != Gitlab::Git::BLANK_SHA && Gitlab::Git.tag_ref?(ref) + tag_name = Gitlab::Git.ref_name(ref) tag = repository.find_tag(tag_name) if tag diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 7e5c991a222..5b9772de168 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -1,13 +1,13 @@ module Gitlab # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor - attr_accessor :users, :labels, :issues, :merge_requests, :snippets, :commits + attr_accessor :users, :labels, :issues, :merge_requests, :snippets, :commits, :commit_ranges include Markdown def initialize - @users, @labels, @issues, @merge_requests, @snippets, @commits = - [], [], [], [], [], [] + @users, @labels, @issues, @merge_requests, @snippets, @commits, @commit_ranges = + [], [], [], [], [], [], [] end def analyze(string, project) @@ -60,6 +60,16 @@ module Gitlab end.reject(&:nil?) end + def commit_ranges_for(project = nil) + commit_ranges.map do |entry| + repo = entry[:project].repository if entry[:project] + if repo && should_lookup?(project, entry[:project]) + from_id, to_id = entry[:id].split(/\.{2,3}/, 2) + [repo.commit(from_id), repo.commit(to_id)] + end + end.reject(&:nil?) + end + private def reference_link(type, identifier, project, _) diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb index a7c83a880f6..9799e54de5d 100644 --- a/lib/gitlab/theme.rb +++ b/lib/gitlab/theme.rb @@ -5,6 +5,7 @@ module Gitlab MODERN = 3 unless const_defined?(:MODERN) GRAY = 4 unless const_defined?(:GRAY) COLOR = 5 unless const_defined?(:COLOR) + BLUE = 6 unless const_defined?(:BLUE) def self.css_class_by_id(id) themes = { @@ -12,7 +13,8 @@ module Gitlab MARS => "ui_mars", MODERN => "ui_modern", GRAY => "ui_gray", - COLOR => "ui_color" + COLOR => "ui_color", + BLUE => "ui_blue" } id ||= Gitlab.config.gitlab.default_theme diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index ab7c8ad89f3..6830d15875a 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -13,6 +13,9 @@ module Gitlab build_issue_url(id) when :merge_request build_merge_request_url(id) + when :note + build_note_url(id) + end end @@ -27,5 +30,31 @@ module Gitlab merge_request = MergeRequest.find(id) merge_request_url(merge_request, host: Gitlab.config.gitlab['url']) end + + def build_note_url(id) + note = Note.find(id) + if note.for_commit? + namespace_project_commit_url(namespace_id: note.project.namespace, + id: note.commit_id, + project_id: note.project, + host: Gitlab.config.gitlab['url'], + anchor: "note_#{note.id}") + elsif note.for_issue? + issue = Issue.find(note.noteable_id) + issue_url(issue, + host: Gitlab.config.gitlab['url'], + anchor: "note_#{note.id}") + elsif note.for_merge_request? + merge_request = MergeRequest.find(note.noteable_id) + merge_request_url(merge_request, + host: Gitlab.config.gitlab['url'], + anchor: "note_#{note.id}") + elsif note.for_project_snippet? + snippet = Snippet.find(note.noteable_id) + snippet_url(snippet, + host: Gitlab.config.gitlab['url'], + anchor: "note_#{note.id}") + end + end end end diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index 714261f815c..1cd3933e4b7 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -3,13 +3,20 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML attr_reader :template alias_method :h, :template - def initialize(template, options = {}) + def initialize(template, color_scheme, options = {}) @template = template + @color_scheme = color_scheme @project = @template.instance_variable_get("@project") @options = options.dup super options end + def preprocess(full_document) + # Redcarpet doesn't allow SMB links when `safe_links_only` is enabled. + # FTP links are allowed, so we trick Redcarpet. + full_document.gsub("smb://", "ftp://smb:") + end + # If project has issue number 39, apostrophe will be linked in # regular text to the issue as Redcarpet will convert apostrophe to # #39; @@ -34,7 +41,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML end formatter = Rugments::Formatters::HTML.new( - cssclass: "code highlight white #{lexer.tag}" + cssclass: "code highlight #{@color_scheme} #{lexer.tag}" ) formatter.format(lexer.lex(code)) end @@ -54,6 +61,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML end def postprocess(full_document) + full_document.gsub!("ftp://smb:", "smb://") + full_document.gsub!("’", "'") unless @template.instance_variable_get("@project_wiki") || @project.nil? full_document = h.create_relative_links(full_document) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 43115915de1..976c4b5f22f 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -29,6 +29,7 @@ namespace :gitlab do check_redis_version check_ruby_version check_git_version + check_active_users finished_checking "GitLab" end @@ -781,6 +782,10 @@ namespace :gitlab do end end + def check_active_users + puts "Active users: #{User.active.count}" + end + def omnibus_gitlab? Dir.pwd == '/opt/gitlab/embedded/service/gitlab-rails' end diff --git a/public/503.html b/public/503.html new file mode 100644 index 00000000000..efdae0f512d --- /dev/null +++ b/public/503.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> + <title>Page took too long to load (503)</title> + <link href="/static.css" media="screen" rel="stylesheet" type="text/css" /> +</head> +<body> + <h1>503</h1> + <h3>Page took too long to load.</h3> + <hr/> + <p>Please contact your GitLab administrator if this problem persists.</p> +</body> +</html> diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index b8820413406..5b967bfcc0c 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -27,17 +27,20 @@ describe Import::GithubController do describe "GET status" do before do @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim') + @org = OpenStruct.new(login: 'company') + @org_repo = OpenStruct.new(login: 'company', full_name: 'company/repo') end it "assigns variables" do @project = create(:project, import_type: 'github', creator_id: user.id) controller.stub_chain(:client, :repos).and_return([@repo]) - controller.stub_chain(:client, :orgs).and_return([]) + controller.stub_chain(:client, :orgs).and_return([@org]) + controller.stub_chain(:client, :org_repos).with(@org.login).and_return([@org_repo]) get :status expect(assigns(:already_added_projects)).to eq([@project]) - expect(assigns(:repos)).to eq([@repo]) + expect(assigns(:repos)).to eq([@repo, @org_repo]) end it "does not show already added project" do diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb new file mode 100644 index 00000000000..0f9780356b1 --- /dev/null +++ b/spec/controllers/uploads_controller_spec.rb @@ -0,0 +1,296 @@ +require 'spec_helper' + +describe UploadsController do + let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + + describe "GET show" do + context "when viewing a user avatar" do + context "when signed in" do + before do + sign_in(user) + end + + context "when the user is blocked" do + before do + user.block + end + + it "redirects to the sign in page" do + get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when not signed in" do + it "responds with status 200" do + get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when viewing a project avatar" do + let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + + context "when the project is public" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + it "responds with status 200" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + it "responds with status 200" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the project is private" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context "when not signed in" do + it "redirects to the sign in page" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "redirects to the sign in page" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response.status).to eq(404) + end + end + end + end + end + + context "when viewing a group avatar" do + let!(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let!(:project) { create(:project, namespace: group) } + + context "when the group has public projects" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + it "responds with status 200" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + it "responds with status 200" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the project doesn't have public projects" do + context "when not signed in" do + it "redirects to the sign in page" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "redirects to the sign in page" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response.status).to eq(404) + end + end + end + end + end + + context "when viewing a note attachment" do + let!(:note) { create(:note, :with_attachment) } + let(:project) { note.project } + + context "when the project is public" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + it "responds with status 200" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + it "responds with status 200" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the project is private" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context "when not signed in" do + it "redirects to the sign in page" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "redirects to the sign in page" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response.status).to eq(404) + end + end + end + end + end + end +end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 6ce1d7446fe..77cd37c22d9 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -40,7 +40,7 @@ FactoryGirl.define do source_branch "master" target_branch "feature" - merge_status :can_be_merged + merge_status "can_be_merged" trait :with_diffs do end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 83d0cc62dbf..f1c33461b55 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -30,6 +30,7 @@ FactoryGirl.define do factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] factory :note_on_merge_request, traits: [:on_merge_request] factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff] + factory :note_on_project_snippet, traits: [:on_project_snippet] trait :on_commit do project factory: :project @@ -52,6 +53,11 @@ FactoryGirl.define do noteable_type "Issue" end + trait :on_project_snippet do + noteable_id 1 + noteable_type "Snippet" + end + trait :with_attachment do attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } end diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb index d1f00a3dd82..67238e3ab76 100644 --- a/spec/features/security/dashboard_access_spec.rb +++ b/spec/features/security/dashboard_access_spec.rb @@ -25,8 +25,8 @@ describe "Dashboard access", feature: true do it { is_expected.to be_denied_for :visitor } end - describe "GET /dashboard/projects" do - subject { projects_dashboard_path } + describe "GET /dashboard/projects/starred" do + subject { starred_dashboard_projects_path } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :user } @@ -52,4 +52,12 @@ describe "Dashboard access", feature: true do it { expect(new_group_path).to be_allowed_for :user } it { expect(new_group_path).to be_denied_for :visitor } end + + describe "GET /profile/groups" do + subject { dashboard_groups_path } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end end diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index 5f254c42e58..2512a9c0e3d 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -1,76 +1,65 @@ require 'spec_helper' -describe "Users Security", feature: true do - describe "Project" do - before do - @u1 = create(:user) - end - - describe "GET /login" do - it { expect(new_user_session_path).not_to be_404_for :visitor } - end - - describe "GET /profile/keys" do - subject { profile_keys_path } +describe "Profile access", feature: true do + before do + @u1 = create(:user) + end - it { is_expected.to be_allowed_for @u1 } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end + describe "GET /login" do + it { expect(new_user_session_path).not_to be_404_for :visitor } + end - describe "GET /profile" do - subject { profile_path } + describe "GET /profile/keys" do + subject { profile_keys_path } - it { is_expected.to be_allowed_for @u1 } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/account" do - subject { profile_account_path } + describe "GET /profile" do + subject { profile_path } - it { is_expected.to be_allowed_for @u1 } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/design" do - subject { design_profile_path } + describe "GET /profile/account" do + subject { profile_account_path } - it { is_expected.to be_allowed_for @u1 } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/history" do - subject { history_profile_path } + describe "GET /profile/design" do + subject { design_profile_path } - it { is_expected.to be_allowed_for @u1 } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/notifications" do - subject { profile_notifications_path } + describe "GET /profile/history" do + subject { history_profile_path } - it { is_expected.to be_allowed_for @u1 } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/groups" do - subject { profile_groups_path } + describe "GET /profile/notifications" do + subject { profile_notifications_path } - it { is_expected.to be_allowed_for @u1 } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 99ff8a32ea5..4c11709ed6e 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -39,24 +39,6 @@ describe ApplicationHelper do end end - describe 'group_icon' do - avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') - - it 'should return an url for the avatar' do - group = create(:group) - group.avatar = File.open(avatar_file_path) - group.save! - expect(group_icon(group.path).to_s). - to match("/uploads/group/avatar/#{ group.id }/gitlab_logo.png") - end - - it 'should give default avatar_icon when no avatar is present' do - group = create(:group) - group.save! - expect(group_icon(group.path)).to match('group_avatar.png') - end - end - describe 'project_icon' do avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index c4a192ac1aa..b392371deb4 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -4,6 +4,8 @@ describe EventsHelper do include ApplicationHelper include GitlabMarkdownHelper + let(:current_user) { create(:user, email: "current@email.com") } + it 'should display one line of plain text without alteration' do input = 'A short, plain note' expect(event_note(input)).to match(input) @@ -50,4 +52,14 @@ describe EventsHelper do expect(event_note(input)).to match(link_url) expect(event_note(input)).to match(expected_link_text) end + + it 'should preserve code color scheme' do + input = "```ruby\ndef test\n 'hello world'\nend\n```" + expected = '<pre class="code highlight white ruby">' \ + "<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \ + " <span class=\"s1\">\'hello world\'</span>\n" \ + "<span class=\"k\">end</span>\n" \ + '</code></pre>' + expect(event_note(input)).to eq(expected) + end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 76fcf888a6a..fd80c615221 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -9,6 +9,7 @@ describe GitlabMarkdownHelper do let(:user) { create(:user, username: 'gfm') } let(:commit) { project.repository.commit } + let(:earlier_commit){ project.repository.commit("HEAD~2") } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:snippet) { create(:project_snippet, project: project) } @@ -53,6 +54,53 @@ describe GitlabMarkdownHelper do to have_selector('a.gfm.foo') end + describe "referencing a commit range" do + let(:expected) { namespace_project_compare_path(project.namespace, project, from: earlier_commit.id, to: commit.id) } + + it "should link using a full id" do + actual = "What happened in #{earlier_commit.id}...#{commit.id}" + expect(gfm(actual)).to match(expected) + end + + it "should link using a short id" do + actual = "What happened in #{earlier_commit.short_id}...#{commit.short_id}" + expected = namespace_project_compare_path(project.namespace, project, from: earlier_commit.short_id, to: commit.short_id) + expect(gfm(actual)).to match(expected) + end + + it "should link inclusively" do + actual = "What happened in #{earlier_commit.id}..#{commit.id}" + expected = namespace_project_compare_path(project.namespace, project, from: "#{earlier_commit.id}^", to: commit.id) + expect(gfm(actual)).to match(expected) + end + + it "should link with adjacent text" do + actual = "(see #{earlier_commit.id}...#{commit.id})" + expect(gfm(actual)).to match(expected) + end + + it "should keep whitespace intact" do + actual = "Changes #{earlier_commit.id}...#{commit.id} dramatically" + expected = /Changes <a.+>#{earlier_commit.id}...#{commit.id}<\/a> dramatically/ + expect(gfm(actual)).to match(expected) + end + + it "should not link with an invalid id" do + actual = expected = "What happened in #{earlier_commit.id.reverse}...#{commit.id.reverse}" + expect(gfm(actual)).to eq(expected) + end + + it "should include a title attribute" do + actual = "What happened in #{earlier_commit.id}...#{commit.id}" + expect(gfm(actual)).to match(/title="Commits #{earlier_commit.id} through #{commit.id}"/) + end + + it "should include standard gfm classes" do + actual = "What happened in #{earlier_commit.id}...#{commit.id}" + expect(gfm(actual)).to match(/class="\s?gfm gfm-commit_range\s?"/) + end + end + describe "referencing a commit" do let(:expected) { namespace_project_commit_path(project.namespace, project, commit) } @@ -616,19 +664,19 @@ describe GitlabMarkdownHelper do it "should generate absolute urls for emoji" do expect(markdown(':smile:')).to( - include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/smile.png)) + include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/#{Emoji.emoji_filename('smile')}.png)) ) end it "should generate absolute urls for emoji if relative url is present" do allow(Gitlab.config.gitlab).to receive(:url).and_return('http://localhost/gitlab/root') - expect(markdown(":smile:")).to include("src=\"http://localhost/gitlab/root/assets/emoji/smile.png") + expect(markdown(":smile:")).to include("src=\"http://localhost/gitlab/root/assets/emoji/#{Emoji.emoji_filename('smile')}.png") end it "should generate absolute urls for emoji if asset_host is present" do allow(Gitlab::Application.config).to receive(:asset_host).and_return("https://cdn.example.com") ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com") - expect(markdown(":smile:")).to include("src=\"https://cdn.example.com/assets/emoji/smile.png") + expect(markdown(":smile:")).to include("src=\"https://cdn.example.com/assets/emoji/#{Emoji.emoji_filename('smile')}.png") end diff --git a/spec/helpers/groups_helper.rb b/spec/helpers/groups_helper.rb new file mode 100644 index 00000000000..3e99ab84ec9 --- /dev/null +++ b/spec/helpers/groups_helper.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe GroupsHelper 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 + group = create(:group) + group.avatar = File.open(avatar_file_path) + group.save! + expect(group_icon(group.path).to_s). + to match("/uploads/group/avatar/#{ group.id }/gitlab_logo.png") + end + + it 'should give default avatar_icon when no avatar is present' do + group = create(:group) + group.save! + expect(group_icon(group.path)).to match('group_avatar.png') + end + end +end diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index a2b05249147..39d46efcbc3 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -20,6 +20,11 @@ describe Gitlab::LDAP::Access do before { Gitlab::LDAP::Person.stub(disabled_via_active_directory?: true) } it { is_expected.to be_falsey } + + it "should block user in GitLab" do + access.allowed? + user.should be_blocked + end end context 'and has no disabled flag in active diretory' do @@ -38,4 +43,4 @@ describe Gitlab::LDAP::Access do end end end -end
\ No newline at end of file +end diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb new file mode 100644 index 00000000000..448cd0c6880 --- /dev/null +++ b/spec/lib/gitlab/note_data_builder_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe 'Gitlab::NoteDataBuilder' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:data) { Gitlab::NoteDataBuilder.build(note, user) } + let(:note_url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors + + before(:each) do + expect(data).to have_key(:object_attributes) + expect(data[:object_attributes]).to have_key(:url) + expect(data[:object_attributes][:url]).to eq(note_url) + expect(data[:object_kind]).to eq('note') + expect(data[:user]).to eq(user.hook_attrs) + end + + describe 'When asking for a note on commit' do + let(:note) { create(:note_on_commit) } + + it 'returns the note and commit-specific data' do + expect(data).to have_key(:commit) + end + end + + describe 'When asking for a note on commit diff' do + let(:note) { create(:note_on_commit_diff) } + + it 'returns the note and commit-specific data' do + expect(data).to have_key(:commit) + end + end + + describe 'When asking for a note on issue' do + let(:issue) { create(:issue, created_at: fixed_time, updated_at: fixed_time) } + let(:note) { create(:note_on_issue, noteable_id: issue.id) } + + it 'returns the note and issue-specific data' do + expect(data).to have_key(:issue) + expect(data[:issue]).to eq(issue.hook_attrs) + end + end + + describe 'When asking for a note on merge request' do + let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) } + let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) } + + it 'returns the note and merge request data' do + expect(data).to have_key(:merge_request) + expect(data[:merge_request]).to eq(merge_request.hook_attrs) + end + end + + describe 'When asking for a note on merge request diff' do + let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) } + let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) } + + it 'returns the note and merge request diff data' do + expect(data).to have_key(:merge_request) + expect(data[:merge_request]).to eq(merge_request.hook_attrs) + end + end + + describe 'When asking for a note on project snippet' do + let!(:snippet) { create(:project_snippet, created_at: fixed_time, updated_at: fixed_time) } + let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) } + + it 'returns the note and project snippet data' do + expect(data).to have_key(:snippet) + expect(data[:snippet]).to eq(snippet.hook_attrs) + end + end +end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 0847c31258c..034f8ee7c45 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -31,6 +31,11 @@ describe Gitlab::ReferenceExtractor do expect(subject.commits).to eq([{ project: nil, id: '98cf0ae3' }]) end + it 'extracts commit ranges' do + subject.analyze('here you go, a commit range: 98cf0ae3...98cf0ae4', nil) + expect(subject.commit_ranges).to eq([{ project: nil, id: '98cf0ae3...98cf0ae4' }]) + end + it 'extracts multiple references and preserves their order' do subject.analyze('@me and @you both care about this', nil) expect(subject.users).to eq([ @@ -100,5 +105,19 @@ describe Gitlab::ReferenceExtractor do expect(extracted[0].sha).to eq(commit.sha) expect(extracted[0].message).to eq(commit.message) end + + it 'accesses valid commit ranges' do + commit = project.repository.commit('master') + earlier_commit = project.repository.commit('master~2') + + subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}", + project) + extracted = subject.commit_ranges_for(project) + expect(extracted.size).to eq(1) + expect(extracted[0][0].sha).to eq(earlier_commit.sha) + expect(extracted[0][0].message).to eq(earlier_commit.message) + expect(extracted[0][1].sha).to eq(commit.sha) + expect(extracted[0][1].message).to eq(commit.message) + end end end diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index 94b2fd5508e..5153ed15af3 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -16,4 +16,62 @@ describe Gitlab::UrlBuilder do expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}" end end + + describe 'When asking for a note on commit' do + let(:note) { create(:note_on_commit) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + end + end + + describe 'When asking for a note on commit diff' do + let(:note) { create(:note_on_commit_diff) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + end + end + + describe 'When asking for a note on issue' do + let(:issue) { create(:issue) } + let(:note) { create(:note_on_issue, noteable_id: issue.id) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}#note_#{note.id}" + end + end + + describe 'When asking for a note on merge request' do + let(:merge_request) { create(:merge_request) } + let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" + end + end + + describe 'When asking for a note on merge request diff' do + let(:merge_request) { create(:merge_request) } + let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" + end + end + + describe 'When asking for a note on project snippet' do + let(:snippet) { create(:project_snippet) } + let(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{snippet.project.path_with_namespace}/snippets/#{note.noteable_id}#note_#{note.id}" + end + end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 3b09c618f2a..e3a3b542358 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -5,6 +5,7 @@ describe Notify do include EmailSpec::Matchers include RepoHelpers + let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } let(:gitlab_sender) { Gitlab.config.gitlab.email_from } let(:recipient) { create(:user, email: 'recipient@example.com') } let(:project) { create(:project) } @@ -23,7 +24,7 @@ describe Notify do shared_examples 'an email sent from GitLab' do it 'is sent from GitLab' do sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq('GitLab') + expect(sender.display_name).to eq(gitlab_sender_display_name) expect(sender.address).to eq(gitlab_sender) end end @@ -182,6 +183,13 @@ describe Notify do context 'for issues' do let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project) } let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: Faker::Lorem.sentence) } + let(:issue_with_image) do + create(:issue, + author: current_user, + assignee: assignee, + project: project, + description: "") + end describe 'that are new' do subject { Notify.new_issue_email(issue.assignee_id, issue.id) } @@ -206,6 +214,22 @@ describe Notify do end end + describe 'that contain images' do + let(:png) { File.read("#{Rails.root}/spec/fixtures/dk.png") } + let(:png_encoded) { Base64::encode64(png) } + + before :each do + file_path = File.join(Rails.root, 'public', 'uploads', issue_with_image.project.path_with_namespace, '12345/test.jpg') + allow(File).to receive(:file?).with(file_path).and_return(true) + allow(File).to receive(:read).with(file_path).and_return(png) + end + + subject { Notify.new_issue_email(issue_with_image.assignee_id, issue_with_image.id) } + it 'replaces attached images with inline images' do + is_expected.to have_body_text URI.encode(png_encoded) + end + end + describe 'that have been reassigned' do subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) } @@ -270,6 +294,14 @@ describe Notify do let(:merge_author) { create(:user) } let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) } let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: Faker::Lorem.sentence) } + let(:merge_request_with_image) do + create(:merge_request, + author: current_user, + assignee: assignee, + source_project: project, + target_project: project, + description: "") + end describe 'that are new' do subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) } @@ -306,6 +338,22 @@ describe Notify do end end + describe 'that are new and contain contain images in the description' do + let(:png) {File.read("#{Rails.root}/spec/fixtures/dk.png")} + let(:png_encoded) { Base64::encode64(png) } + + before :each do + file_path = File.join(Rails.root, 'public', 'uploads', merge_request_with_image.project.path_with_namespace, '/12345/test.jpg') + allow(File).to receive(:file?).with(file_path).and_return(true) + allow(File).to receive(:read).with(file_path).and_return(png) + end + + subject { Notify.new_merge_request_email(merge_request_with_image.assignee_id, merge_request_with_image.id) } + it 'replaces attached images with inline images' do + is_expected.to have_body_text URI.encode(png_encoded) + end + end + describe 'that are reassigned' do subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) } @@ -414,9 +462,12 @@ describe Notify do describe 'project access changed' do let(:project) { create(:project) } let(:user) { create(:user) } - let(:project_member) { create(:project_member, - project: project, - user: user) } + let(:project_member) do + create(:project_member, + project: project, + user: user) + end + subject { Notify.project_access_granted_email(project_member.id) } it_behaves_like 'an email sent from GitLab' @@ -456,6 +507,32 @@ describe Notify do end end + describe 'on a commit that contains an image' do + let(:commit) { project.repository.commit } + let(:note_with_image) do + create(:note, + project: project, + author: note_author, + note: "") + end + + let(:png) {File.read("#{Rails.root}/spec/fixtures/dk.png")} + let(:png_encoded) { Base64::encode64(png) } + + before :each do + file_path = File.join(Rails.root, 'public', 'uploads', note_with_image.project.path_with_namespace, '12345/test.jpg') + allow(File).to receive(:file?).with(file_path).and_return(true) + allow(File).to receive(:read).with(file_path).and_return(png) + allow(Note).to receive(:find).with(note_with_image.id).and_return(note_with_image) + allow(note_with_image).to receive(:noteable).and_return(commit) + end + + subject { Notify.note_commit_email(recipient.id, note_with_image.id) } + it 'replaces attached images with inline images' do + is_expected.to have_body_text URI.encode(png_encoded) + end + end + describe 'on a commit' do let(:commit) { project.repository.commit } @@ -568,9 +645,10 @@ describe Notify do let(:user) { create(:user) } let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) } let(:commits) { Commit.decorate(compare.commits) } - let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: commits.first, to: commits.last) } + let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base), to: Commit.new(compare.head)) } + let(:send_from_committer_email) { false } - subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) } + subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare, false, send_from_committer_email) } it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -583,7 +661,7 @@ describe Notify do end it 'has the correct subject' do - is_expected.to have_subject /#{commits.length} new commits pushed to repository/ + is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/ end it 'includes commits list' do @@ -597,6 +675,58 @@ describe Notify do it 'contains a link to the diff' do is_expected.to have_body_text /#{diff_path}/ end + + it 'doesn not contain the misleading footer' do + is_expected.not_to have_body_text /you are a member of/ + end + + context "when set to send from committer email if domain matches" do + + let(:send_from_committer_email) { true } + + before do + allow(Gitlab.config.gitlab).to receive(:host).and_return("gitlab.corp.company.com") + end + + context "when the committer email domain is within the GitLab domain" do + + before do + user.update_attribute(:email, "user@company.com") + user.confirm! + end + + it "is sent from the committer email" do + sender = subject.header[:from].addrs[0] + expect(sender.address).to eq(user.email) + end + end + + context "when the committer email domain is not completely within the GitLab domain" do + + before do + user.update_attribute(:email, "user@something.company.com") + user.confirm! + end + + it "is sent from the default email" do + sender = subject.header[:from].addrs[0] + expect(sender.address).to eq(gitlab_sender) + end + end + + context "when the committer email domain is outside the GitLab domain" do + + before do + user.update_attribute(:email, "user@mpany.com") + user.confirm! + end + + it "is sent from the default email" do + sender = subject.header[:from].addrs[0] + expect(sender.address).to eq(gitlab_sender) + end + end + end end describe 'email on push with a single commit' do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index cb43fdb7fc7..d1027f64d13 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -2,16 +2,17 @@ # # Table name: application_settings # -# id :integer not null, primary key -# default_projects_limit :integer -# default_branch_protection :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) +# 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) +# default_branch_protection :integer default(2) +# twitter_sharing_enabled :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/asana_service_spec.rb b/spec/models/asana_service_spec.rb index 83e39f87f33..13c8d54a2af 100644 --- a/spec/models/asana_service_spec.rb +++ b/spec/models/asana_service_spec.rb @@ -2,14 +2,19 @@ # # 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 +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index cd34e006ebe..91730da1eec 100644 --- a/spec/models/project_services/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb @@ -5,15 +5,16 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/buildbox_service_spec.rb b/spec/models/project_services/buildbox_service_spec.rb index c246e1c9d41..39d7df54cf0 100644 --- a/spec/models/project_services/buildbox_service_spec.rb +++ b/spec/models/project_services/buildbox_service_spec.rb @@ -5,15 +5,16 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index 2ec167a7330..73f68301a34 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -5,15 +5,16 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 5f665fadfff..d44064bbe6a 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -5,15 +5,16 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb index fcb33b11732..8bfb19e524b 100644 --- a/spec/models/project_services/gitlab_ci_service_spec.rb +++ b/spec/models/project_services/gitlab_ci_service_spec.rb @@ -5,15 +5,16 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index c474f4a2d95..959044dc727 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -2,16 +2,21 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # + require 'spec_helper' describe GitlabIssueTrackerService do diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb new file mode 100644 index 00000000000..8ab847e6432 --- /dev/null +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -0,0 +1,217 @@ +# == 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 +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# + +require 'spec_helper' + +describe HipchatService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Execute" do + let(:hipchat) { HipchatService.new } + let(:user) { create(:user, username: 'username') } + let(:project) { create(:project, name: 'project') } + let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' } + let(:project_name) { project.name_with_namespace.gsub(/\s/, '') } + + before(:each) do + hipchat.stub( + project_id: project.id, + project: project, + room: 123456, + server: 'https://hipchat.example.com', + token: 'verySecret' + ) + WebMock.stub_request(:post, api_url) + end + + context 'push events' do + let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + it "should call Hipchat API for push events" do + hipchat.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "should create a push message" do + message = hipchat.send(:create_push_message, push_sample_data) + + obj_attr = push_sample_data[:object_attributes] + branch = push_sample_data[:ref].gsub('refs/heads/', '') + expect(message).to include("#{user.name} pushed to branch " \ + "<a href=\"#{project.web_url}/commits/#{branch}\">#{branch}</a> of " \ + "<a href=\"#{project.web_url}\">#{project_name}</a>") + end + end + + context 'tag_push events' do + let(:push_sample_data) { Gitlab::PushDataBuilder.build(project, user, Gitlab::Git::BLANK_SHA, '1' * 40, 'refs/tags/test', []) } + + it "should call Hipchat API for tag push events" do + hipchat.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "should create a tag push message" do + message = hipchat.send(:create_push_message, push_sample_data) + + obj_attr = push_sample_data[:object_attributes] + expect(message).to eq("#{user.name} pushed new tag " \ + "<a href=\"#{project.web_url}/commits/test\">test</a> to " \ + "<a href=\"#{project.web_url}\">#{project_name}</a>\n") + end + end + + context 'issue events' do + let(:issue) { create(:issue, title: 'Awesome issue', description: 'please fix') } + let(:issue_service) { Issues::CreateService.new(project, user) } + let(:issues_sample_data) { issue_service.hook_data(issue, 'open') } + + it "should call Hipchat API for issue events" do + hipchat.execute(issues_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "should create an issue message" do + message = hipchat.send(:create_issue_message, issues_sample_data) + + obj_attr = issues_sample_data[:object_attributes] + expect(message).to eq("#{user.name} opened " \ + "<a href=\"#{obj_attr[:url]}\">issue ##{obj_attr["iid"]}</a> in " \ + "<a href=\"#{project.web_url}\">#{project_name}</a>: " \ + "<b>Awesome issue</b>" \ + "<pre>please fix</pre>") + end + end + + context 'merge request events' do + let(:merge_request) { create(:merge_request, description: 'please fix', title: 'Awesome merge request', target_project: project, source_project: project) } + let(:merge_service) { MergeRequests::CreateService.new(project, user) } + let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') } + + it "should call Hipchat API for merge requests events" do + hipchat.execute(merge_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "should create a merge request message" do + message = hipchat.send(:create_merge_request_message, + merge_sample_data) + + obj_attr = merge_sample_data[:object_attributes] + expect(message).to eq("#{user.name} opened " \ + "<a href=\"#{obj_attr[:url]}\">merge request ##{obj_attr["iid"]}</a> in " \ + "<a href=\"#{project.web_url}\">#{project_name}</a>: " \ + "<b>Awesome merge request</b>" \ + "<pre>please fix</pre>") + end + end + + context "Note events" do + let(:user) { create(:user) } + let(:project) { create(:project, creator_id: user.id) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:snippet) { create(:project_snippet, project: project) } + let(:commit_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } + let(:merge_request_note) { create(:note_on_merge_request, noteable_id: merge_request.id, note: "merge request note") } + let(:issue_note) { create(:note_on_issue, noteable_id: issue.id, note: "issue note")} + let(:snippet_note) { create(:note_on_project_snippet, noteable_id: snippet.id, note: "snippet note") } + + it "should call Hipchat API for commit comment events" do + data = Gitlab::NoteDataBuilder.build(commit_note, user) + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + commit_id = Commit.truncate_sha(data[:commit][:id]) + title = hipchat.send(:format_title, data[:commit][:message]) + + expect(message).to eq("#{user.name} commented on " \ + "<a href=\"#{obj_attr[:url]}\">commit #{commit_id}</a> in " \ + "<a href=\"#{project.web_url}\">#{project_name}</a>: " \ + "#{title}" \ + "<pre>a comment on a commit</pre>") + end + + it "should call Hipchat API for merge request comment events" do + data = Gitlab::NoteDataBuilder.build(merge_request_note, user) + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + merge_id = data[:merge_request]['iid'] + title = data[:merge_request]['title'] + + expect(message).to eq("#{user.name} commented on " \ + "<a href=\"#{obj_attr[:url]}\">merge request ##{merge_id}</a> in " \ + "<a href=\"#{project.web_url}\">#{project_name}</a>: " \ + "<b>#{title}</b>" \ + "<pre>merge request note</pre>") + end + + it "should call Hipchat API for issue comment events" do + data = Gitlab::NoteDataBuilder.build(issue_note, user) + hipchat.execute(data) + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + issue_id = data[:issue]['iid'] + title = data[:issue]['title'] + + expect(message).to eq("#{user.name} commented on " \ + "<a href=\"#{obj_attr[:url]}\">issue ##{issue_id}</a> in " \ + "<a href=\"#{project.web_url}\">#{project_name}</a>: " \ + "<b>#{title}</b>" \ + "<pre>issue note</pre>") + end + + it "should call Hipchat API for snippet comment events" do + data = Gitlab::NoteDataBuilder.build(snippet_note, user) + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + snippet_id = data[:snippet]['id'] + title = data[:snippet]['title'] + + expect(message).to eq("#{user.name} commented on " \ + "<a href=\"#{obj_attr[:url]}\">snippet ##{snippet_id}</a> in " \ + "<a href=\"#{project.web_url}\">#{project_name}</a>: " \ + "<b>#{title}</b>" \ + "<pre>snippet note</pre>") + end + end + end +end diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index bbd5245ad34..d55399bc360 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -2,14 +2,19 @@ # # 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 +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 6ef4d036c3f..355911e6377 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -2,14 +2,19 @@ # # 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 +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index bb2e72c3ac1..5a18fd09bfc 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -5,15 +5,16 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb index a23a7cc068e..8bca1fef44c 100644 --- a/spec/models/project_services/slack_service/issue_message_spec.rb +++ b/spec/models/project_services/slack_service/issue_message_spec.rb @@ -6,7 +6,8 @@ describe SlackService::IssueMessage do let(:args) { { user: { - username: 'username' + name: 'Test User', + username: 'Test User' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -29,8 +30,8 @@ describe SlackService::IssueMessage do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - 'username opened issue <url|#100> in <somewhere.com|project_name>: '\ - 'Issue title') + 'Test User opened <url|issue #100> in <somewhere.com|project_name>: '\ + '*Issue title*') expect(subject.attachments).to eq([ { text: "issue description", @@ -47,8 +48,8 @@ describe SlackService::IssueMessage do end it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - 'username closed issue <url|#100> in <somewhere.com|project_name>: '\ - 'Issue title') + 'Test User closed <url|issue #100> in <somewhere.com|project_name>: '\ + '*Issue title*') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb index 25d03cd8736..aeb408aa766 100644 --- a/spec/models/project_services/slack_service/merge_message_spec.rb +++ b/spec/models/project_services/slack_service/merge_message_spec.rb @@ -6,13 +6,14 @@ describe SlackService::MergeMessage do let(:args) { { user: { - username: 'username' + name: 'Test User', + username: 'Test User' }, project_name: 'project_name', project_url: 'somewhere.com', object_attributes: { - title: 'Issue title', + title: "Issue title\nSecond line", id: 10, iid: 100, assignee_id: 1, @@ -30,8 +31,8 @@ describe SlackService::MergeMessage do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'username opened merge request <somewhere.com/merge_requests/100|#100> '\ - 'in <somewhere.com|project_name>') + 'Test User opened <somewhere.com/merge_requests/100|merge request #100> '\ + 'in <somewhere.com|project_name>: *Issue title*') expect(subject.attachments).to be_empty end end @@ -42,8 +43,8 @@ describe SlackService::MergeMessage do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'username closed merge request <somewhere.com/merge_requests/100|#100> '\ - 'in <somewhere.com|project_name>') + 'Test User closed <somewhere.com/merge_requests/100|merge request #100> '\ + 'in <somewhere.com|project_name>: *Issue title*') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb new file mode 100644 index 00000000000..21fb575480b --- /dev/null +++ b/spec/models/project_services/slack_service/note_message_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper' + +describe SlackService::NoteMessage do + let(:color) { '#345' } + + before do + @args = { + user: { + name: 'Test User', + username: 'username', + avatar_url: 'http://fakeavatar' + }, + project_name: 'project_name', + project_url: 'somewhere.com', + repository: { + name: 'project_name', + url: 'somewhere.com', + }, + object_attributes: { + id: 10, + note: 'comment on a commit', + url: 'url', + noteable_type: 'Commit' + } + } + end + + context 'commit notes' do + before do + @args[:object_attributes][:note] = 'comment on a commit' + @args[:object_attributes][:noteable_type] = 'Commit' + @args[:commit] = { + id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23', + message: "Added a commit message\ndetails\n123\n" + } + end + + it 'returns a message regarding notes on commits' do + message = SlackService::NoteMessage.new(@args) + expect(message.pretext).to eq("Test User commented on " \ + "<url|commit 5f163b2b> in <somewhere.com|project_name>: " \ + "*Added a commit message*") + expected_attachments = [ + { + text: "comment on a commit", + color: color, + } + ] + expect(message.attachments).to eq(expected_attachments) + end + end + + context 'merge request notes' do + before do + @args[:object_attributes][:note] = 'comment on a merge request' + @args[:object_attributes][:noteable_type] = 'MergeRequest' + @args[:merge_request] = { + id: 1, + iid: 30, + title: "merge request title\ndetails\n" + } + end + it 'returns a message regarding notes on a merge request' do + message = SlackService::NoteMessage.new(@args) + expect(message.pretext).to eq("Test User commented on " \ + "<url|merge request #30> in <somewhere.com|project_name>: " \ + "*merge request title*") + expected_attachments = [ + { + text: "comment on a merge request", + color: color, + } + ] + expect(message.attachments).to eq(expected_attachments) + end + end + + context 'issue notes' do + before do + @args[:object_attributes][:note] = 'comment on an issue' + @args[:object_attributes][:noteable_type] = 'Issue' + @args[:issue] = { + id: 1, + iid: 20, + title: "issue title\ndetails\n" + } + end + + it 'returns a message regarding notes on an issue' do + message = SlackService::NoteMessage.new(@args) + expect(message.pretext).to eq( + "Test User commented on " \ + "<url|issue #20> in <somewhere.com|project_name>: " \ + "*issue title*") + expected_attachments = [ + { + text: "comment on an issue", + color: color, + } + ] + expect(message.attachments).to eq(expected_attachments) + end + end + + context 'project snippet notes' do + before do + @args[:object_attributes][:note] = 'comment on a snippet' + @args[:object_attributes][:noteable_type] = 'Snippet' + @args[:snippet] = { + id: 5, + title: "snippet title\ndetails\n" + } + end + + it 'returns a message regarding notes on a project snippet' do + message = SlackService::NoteMessage.new(@args) + expect(message.pretext).to eq("Test User commented on " \ + "<url|snippet #5> in <somewhere.com|project_name>: " \ + "*snippet title*") + expected_attachments = [ + { + text: "comment on a snippet", + color: color, + } + ] + expect(message.attachments).to eq(expected_attachments) + end + end +end diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb index ef0e7a6ee30..10963481a12 100644 --- a/spec/models/project_services/slack_service/push_message_spec.rb +++ b/spec/models/project_services/slack_service/push_message_spec.rb @@ -39,9 +39,29 @@ describe SlackService::PushMessage do end end + context 'tag push' do + let(:args) { + { + after: 'after', + before: Gitlab::Git::BLANK_SHA, + project_name: 'project_name', + ref: 'refs/tags/new_tag', + user_name: 'user_name', + project_url: 'url' + } + } + + it 'returns a message regarding pushes' do + expect(subject.pretext).to eq('user_name pushed new tag ' \ + '<url/commits/new_tag|new_tag> to ' \ + '<url|project_name>') + expect(subject.attachments).to be_empty + end + end + context 'new branch' do before do - args[:before] = '000000' + args[:before] = Gitlab::Git::BLANK_SHA end it 'returns a message regarding a new branch' do @@ -55,7 +75,7 @@ describe SlackService::PushMessage do context 'removed branch' do before do - args[:after] = '000000' + args[:after] = Gitlab::Git::BLANK_SHA end it 'returns a message regarding a removed branch' do diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 9024e53f0ff..c36506644b3 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -5,15 +5,16 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' @@ -75,16 +76,16 @@ describe SlackService do 'open') end - it "should call Slack API for pull requests" do + it "should call Slack API for push events" do slack.execute(push_sample_data) - WebMock.should have_requested(:post, webhook_url).once + expect(WebMock).to have_requested(:post, webhook_url).once end it "should call Slack API for issue events" do slack.execute(@issues_sample_data) - WebMock.should have_requested(:post, webhook_url).once + expect(WebMock).to have_requested(:post, webhook_url).once end it "should call Slack API for merge requests events" do @@ -96,10 +97,10 @@ describe SlackService do it 'should use the username as an option for slack when configured' do slack.stub(username: username) expect(Slack::Notifier).to receive(:new). - with(webhook_url, username: username). - and_return( - double(:slack_service).as_null_object - ) + with(webhook_url, username: username). + and_return( + double(:slack_service).as_null_object + ) slack.execute(push_sample_data) end @@ -113,4 +114,57 @@ describe SlackService do slack.execute(push_sample_data) end end + + describe "Note events" do + let(:slack) { SlackService.new } + let(:user) { create(:user) } + let(:project) { create(:project, creator_id: user.id) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:snippet) { create(:project_snippet, project: project) } + let(:commit_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } + let(:merge_request_note) { create(:note_on_merge_request, noteable_id: merge_request.id, note: "merge request note") } + let(:issue_note) { create(:note_on_issue, noteable_id: issue.id, note: "issue note")} + let(:snippet_note) { create(:note_on_project_snippet, noteable_id: snippet.id, note: "snippet note") } + let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } + + before do + slack.stub( + project: project, + project_id: project.id, + service_hook: true, + webhook: webhook_url + ) + + WebMock.stub_request(:post, webhook_url) + end + + it "should call Slack API for commit comment events" do + data = Gitlab::NoteDataBuilder.build(commit_note, user) + slack.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + it "should call Slack API for merge request comment events" do + data = Gitlab::NoteDataBuilder.build(merge_request_note, user) + slack.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + it "should call Slack API for issue comment events" do + data = Gitlab::NoteDataBuilder.build(issue_note, user) + slack.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + it "should call Slack API for snippet comment events" do + data = Gitlab::NoteDataBuilder.build(snippet_note, user) + slack.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index eeb0f3d9ee0..b3a38f6c5b9 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -18,4 +18,27 @@ describe Repository do it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } end + + context :timestamps_by_user_log do + before do + Date.stub(:today).and_return(Date.new(2015, 03, 01)) + end + + describe 'single e-mail for user' do + let(:user) { create(:user, email: sample_commit.author_email) } + + subject { repository.timestamps_by_user_log(user) } + + it { is_expected.to eq(["2014-08-06", "2014-07-31", "2014-07-31"]) } + end + + describe 'multiple emails for user' do + let(:email_alias) { create(:email, email: another_sample_commit.author_email) } + let(:user) { create(:user, email: sample_commit.author_email, emails: [email_alias]) } + + subject { repository.timestamps_by_user_log(user) } + + it { is_expected.to eq(["2015-01-10", "2014-08-06", "2014-07-31", "2014-07-31"]) } + end + end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index cc047a20dd2..735652aea78 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -2,19 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean -# issues_events :boolean -# merge_requests_events :boolean -# tag_push_events :boolean +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) # require 'spec_helper' diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 29d0c24e87e..10e90cae143 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,47 +2,53 @@ # # Table name: users # -# id :integer not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(255) default(""), not null -# reset_password_token :string(255) -# reset_password_sent_at :datetime -# remember_created_at :datetime -# sign_in_count :integer default(0) -# current_sign_in_at :datetime -# last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) -# created_at :datetime -# updated_at :datetime -# name :string(255) -# admin :boolean default(FALSE), not null -# projects_limit :integer default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) -# theme_id :integer default(1), not null -# bio :string(255) -# failed_attempts :integer default(0) -# locked_at :datetime -# username :string(255) -# can_create_group :boolean default(TRUE), not null -# can_create_team :boolean default(TRUE), not null -# state :string(255) -# color_scheme_id :integer default(1), not null -# notification_level :integer default(1), not null -# password_expires_at :datetime -# created_by_id :integer -# avatar :string(255) -# confirmation_token :string(255) -# confirmed_at :datetime -# confirmation_sent_at :datetime -# 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) +# id :integer not null, primary key +# email :string(255) default(""), not null +# encrypted_password :string(255) default(""), not null +# reset_password_token :string(255) +# reset_password_sent_at :datetime +# remember_created_at :datetime +# sign_in_count :integer default(0) +# current_sign_in_at :datetime +# last_sign_in_at :datetime +# current_sign_in_ip :string(255) +# last_sign_in_ip :string(255) +# created_at :datetime +# updated_at :datetime +# name :string(255) +# admin :boolean default(FALSE), not null +# projects_limit :integer default(10) +# skype :string(255) default(""), not null +# linkedin :string(255) default(""), not null +# twitter :string(255) default(""), not null +# authentication_token :string(255) +# theme_id :integer default(1), not null +# bio :string(255) +# failed_attempts :integer default(0) +# locked_at :datetime +# username :string(255) +# can_create_group :boolean default(TRUE), not null +# can_create_team :boolean default(TRUE), not null +# state :string(255) +# color_scheme_id :integer default(1), not null +# 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 +# confirmation_sent_at :datetime +# unconfirmed_email :string(255) +# hide_no_ssh_key :boolean default(FALSE) +# website_url :string(255) default(""), not null +# github_access_token :string(255) +# gitlab_access_token :string(255) +# notification_email :string(255) +# hide_no_password :boolean default(FALSE) +# password_automatically_set :boolean default(FALSE) +# bitbucket_access_token :string(255) +# bitbucket_access_token_secret :string(255) # require 'spec_helper' diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index e264072b573..1b1e3ca5f8b 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -197,15 +197,6 @@ describe GitPushService do service.execute(project, user, @blankrev, @newrev, 'refs/heads/other') end - - it "finds references in the first push to a default branch" do - allow(project.repository).to receive(:commits_between).with(@blankrev, @newrev).and_return([]) - allow(project.repository).to receive(:commits).with(@newrev).and_return([commit]) - - expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author, project) - - service.execute(project, user, @blankrev, @newrev, 'refs/heads/master') - end end describe "closing issues from pushed commits" do diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb index 504213e667f..a97c55011c9 100644 --- a/spec/services/issues/bulk_update_service_spec.rb +++ b/spec/services/issues/bulk_update_service_spec.rb @@ -21,10 +21,8 @@ describe Issues::BulkUpdateService do create(:issue, project: @project) end @params = { - update: { - status: 'closed', - issues_ids: @issues.map(&:id) - } + state_event: 'close', + issues_ids: @issues.map(&:id) } end @@ -46,10 +44,8 @@ describe Issues::BulkUpdateService do create(:closed_issue, project: @project) end @params = { - update: { - status: 'reopen', - issues_ids: @issues.map(&:id) - } + state_event: 'reopen', + issues_ids: @issues.map(&:id) } end @@ -69,10 +65,8 @@ describe Issues::BulkUpdateService do before do @new_assignee = create :user @params = { - update: { - issues_ids: [issue.id], - assignee_id: @new_assignee.id - } + issues_ids: [issue.id], + assignee_id: @new_assignee.id } end @@ -88,7 +82,7 @@ describe Issues::BulkUpdateService do @project.issues.first.update_attribute(:assignee, @new_assignee) expect(@project.issues.first.assignee).not_to be_nil - @params[:update][:assignee_id] = -1 + @params[:assignee_id] = -1 Issues::BulkUpdateService.new(@project, @user, @params).execute expect(@project.issues.first.assignee).to be_nil @@ -98,7 +92,7 @@ describe Issues::BulkUpdateService do @project.issues.first.update_attribute(:assignee, @new_assignee) expect(@project.issues.first.assignee).not_to be_nil - @params[:update][:assignee_id] = '' + @params[:assignee_id] = '' Issues::BulkUpdateService.new(@project, @user, @params).execute expect(@project.issues.first.assignee).not_to be_nil @@ -110,10 +104,8 @@ describe Issues::BulkUpdateService do before do @milestone = create :milestone @params = { - update: { - issues_ids: [issue.id], - milestone_id: @milestone.id - } + issues_ids: [issue.id], + milestone_id: @milestone.id } end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 2830da87814..879df0c9c67 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -61,7 +61,7 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('new commit') } + it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') } it { expect(@fork_merge_request).to be_open } end diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb index 4c4775da692..aadf791bf3f 100644 --- a/spec/support/repo_helpers.rb +++ b/spec/support/repo_helpers.rb @@ -43,6 +43,25 @@ eos ) end + def another_sample_commit + OpenStruct.new( + id: "e56497bb5f03a90a51293fc6d516788730953899", + parent_id: '4cd80ccab63c82b4bad16faa5193fbd2aa06df40', + author_full_name: "Sytse Sijbrandij", + author_email: "sytse@gitlab.com", + files_changed_count: 1, + message: <<eos +Add directory structure for tree_helper spec + +This directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module + +See [merge request #275](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/275#note_732774) + +See merge request !2 +eos + ) + end + def sample_big_commit OpenStruct.new( id: "913c66a37b4a45b9769037c55c2d238bd0942d2e", |