diff options
157 files changed, 2128 insertions, 927 deletions
diff --git a/.travis.yml b/.travis.yml index b2449d4189e..6bff3752b2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ env: - TASK=jasmine:ci DB=mysql - TASK=spinach DB=postgresql - TASK=spec DB=postgresql + - TASK=jasmine:ci DB=postgresql before_install: - sudo apt-get install libicu-dev -y branches: diff --git a/CHANGELOG b/CHANGELOG index 51f9523da73..5b0d38b9898 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,15 @@ v 6.7.0 - - Add support for Gemnasium as a Project Service + - Add support for Gemnasium as a Project Service (Olivier Gonzalez) - Add edit file button to MergeRequest diff + - Public groups (Jason Hollingsworth) + - Cleaner headers in Notification Emails (Pierre de La Morinerie) + - Blob and tree gfm links to anchors work + +v 6.6.2 + - Fix 500 error on branch/tag create or remove via UI + +v 6.6.1 + - Fix 500 error on files tab if submodules presents v 6.6.0 - Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d414bf962c..c1788657b95 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,6 +66,7 @@ If you can, please submit a merge request with the fix or improvements including 1. If the MR changes the UI it should include before and after screenshots 1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feedback items](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR 1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion +1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md). The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab.com team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features. @@ -15,6 +15,9 @@ gem 'rails-observers' gem 'actionpack-page_caching' gem 'actionpack-action_caching' +# Default values for AR models +gem "default_value_for", "~> 3.0.0" + # Supported DBs gem "mysql2", group: :mysql gem "pg", group: :postgres @@ -164,7 +167,7 @@ group :development do gem "annotate", "~> 2.6.0.beta2" gem "letter_opener" gem 'quiet_assets', '~> 1.0.1' - gem 'rack-mini-profiler' + gem 'rack-mini-profiler', require: false # Better errors handler gem 'better_errors' diff --git a/Gemfile.lock b/Gemfile.lock index f3c2ce835c5..2cf0929fe79 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,6 +101,8 @@ GEM daemons (1.1.9) database_cleaner (1.2.0) debug_inspector (0.0.2) + default_value_for (3.0.0) + activerecord (>= 3.2.0, < 5.0) descendants_tracker (0.0.3) devise (3.0.4) bcrypt-ruby (~> 3.0) @@ -340,7 +342,7 @@ GEM rack-attack (2.3.0) rack rack-cors (0.2.9) - rack-mini-profiler (0.1.31) + rack-mini-profiler (0.9.0) rack (>= 1.1.3) rack-mount (0.8.3) rack (>= 1.0.0) @@ -570,6 +572,7 @@ DEPENDENCIES coveralls d3_rails (~> 3.1.4) database_cleaner + default_value_for (~> 3.0.0) devise (= 3.0.4) devise-async (= 0.8.0) email_spec diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee index 9f55a1e6368..5f53439ca4b 100644 --- a/app/assets/javascripts/commit.js.coffee +++ b/app/assets/javascripts/commit.js.coffee @@ -1,6 +1,6 @@ class Commit constructor: -> - $('.files .file').each -> + $('.files .diff-file').each -> new CommitFile(this) @Commit = Commit diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee index 9cf4dba815b..69c731ff1a1 100644 --- a/app/assets/javascripts/main.js.coffee +++ b/app/assets/javascripts/main.js.coffee @@ -117,11 +117,11 @@ $ -> # Commit show suppressed diff - $(".content").on "click", ".supp_diff_link", -> + $(".diff-content").on "click", ".supp_diff_link", -> $(@).next('table').show() $(@).remove() - $(".content").on "click", ".js-details-expand", -> + $(".diff-content").on "click", ".js-details-expand", -> $(@).next('.js-details-contain').removeClass("hide") $(@).remove() diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index d5522f00f50..7fdbd5907b0 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -37,6 +37,7 @@ @import "generic/issue_box.scss"; @import "generic/files.scss"; @import "generic/lists.scss"; +@import "generic/flash.scss"; @import "generic/forms.scss"; @import "generic/selects.scss"; @import "generic/highlight.scss"; @@ -47,6 +48,7 @@ @import "sections/header.scss"; @import "sections/nav.scss"; @import "sections/commits.scss"; +@import "sections/diff.scss"; @import "sections/issues.scss"; @import "sections/projects.scss"; @import "sections/snippets.scss"; diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss index 1cbd7439835..3536a68f416 100644 --- a/app/assets/stylesheets/generic/blocks.scss +++ b/app/assets/stylesheets/generic/blocks.scss @@ -2,3 +2,18 @@ background: #f9f9f9; padding: 15px; } + +.centered-light-block { + text-align: center; + color: #888; + margin: 20px; +} + +.nothing-here-block { + text-align: center; + padding: 20px; + color: #666; + font-weight: normal; + font-size: 16px; + line-height: 36px; +} diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index 4cb41a79ff0..dd0a2938cfe 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -158,6 +158,14 @@ color: #468847; } } + + &.btn-grouped { + margin-right: 7px; + float: left; + &:last-child { + margin-right: 0px; + } + } } .btn-block { @@ -169,9 +177,8 @@ } } -.btn, .btn-group { - &.grouped { + &.btn-grouped { margin-right: 7px; float: left; &:last-child { diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 7afa74400c9..6183cefa594 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -11,8 +11,6 @@ .bgred { background: #F2DEDE!important } /** COMMON CLASSES **/ -.left { float:left } - .prepend-top-10 { margin-top:10px } .prepend-top-20 { margin-top:20px } .prepend-left-10 { margin-left:10px } @@ -24,32 +22,9 @@ .append-bottom-20 { margin-bottom:20px } .inline { display: inline-block } -.padded { padding:20px } -.ipadded { padding:20px!important } -.lborder { border-left:1px solid #eee } -.underlined_link { text-decoration: underline; } +.underlined-link { text-decoration: underline; } .hint { font-style: italic; color: #999; } .light { color: #888 } -.tiny { font-weight: normal } -.vtop { vertical-align: top !important; } - - -/** ALERT MESSAGES **/ -.alert.alert-disabled { - background: #EEE; - color: #777; - border-color: #DDD; -} - -/** HELPERS **/ -.nothing_here_message { - text-align: center; - padding: 20px; - color: #666; - font-weight: normal; - font-size: 16px; - line-height: 36px; -} .slead { color: #666; @@ -59,53 +34,23 @@ line-height: 24px; } - .tab-content { overflow: visible; } -@media (max-width: 1200px) { - .only-wide { - display: none; - } -} - -pre.well-pre { - border: 1px solid #EEE; - background: #f9f9f9; - border-radius: 0; - color: #555; -} - -.input-append .btn.active, .input-prepend .btn.active { - background: #CCC; - border-color: #BBB; - text-shadow: 0 1px 1px #fff; - font-weight: bold; - @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15)); -} - -/** Big Labels **/ -.state-label { - font-size: 14px; - padding: 9px 25px; - text-align: center; - text-shadow: none; - margin-right: 20px; - - &.state-label-blue { - background: #31708f; - color: #FFF; - } - - &.state-label-green { - background: #4A4; - color: #FFF; +pre { + &.clean { + background: none; + border: none; + margin: 0; + padding: 0; } - &.state-label-red { - background: #DA4E49; - color: #FFF; + &.well-pre { + border: 1px solid #EEE; + background: #f9f9f9; + border-radius: 0; + color: #555; } } @@ -135,31 +80,6 @@ pre.well-pre { } /** FLASH message **/ -.flash-container { - display: none; - cursor: pointer; - margin: 0; - text-align: center; - color: #fff; - font-size: 14px; - position: fixed; - bottom: 0; - width: 100%; - opacity: 0.8; - z-index: 100; - - .flash-notice { - background: #49C; - padding: 10px; - text-shadow: 0 1px 1px #178; - } - - .flash-alert { - background: #C67; - text-shadow: 0 1px 1px #945; - padding: 10px; - } -} .author_link { color: $link_color; } @@ -279,22 +199,6 @@ li.note { cursor: pointer; } -.merge-request, -.issue { - &.today{ - background: #EFE; - border-color: #CEC; - } - &.closed { - background: #F5f5f5; - border-color: #E5E5E5; - } - &.merged { - background: #F5f5f5; - border-color: #E5E5E5; - } -} - .git_error_tips { @extend .col-md-6; text-align: left; @@ -358,15 +262,6 @@ li.note { } } -pre { - &.clean { - background: none; - border: none; - margin: 0; - padding: 0; - } -} - .milestone { &.milestone-closed { background: #eee; @@ -450,40 +345,6 @@ table { margin-bottom: 20px; } -.ajax-users-select { - width: 400px; - - &.input-large { - width: 210px; - } - - &.input-clamp { - max-width: 100%; - } -} - -.user-result { - .user-image { - float: left; - } - .user-name { - } - .user-username { - color: #999; - } -} - -.namespace-result { - .namespace-kind { - color: #AAA; - font-weight: normal; - } - .namespace-path { - margin-left: 10px; - font-weight: bolder; - } -} - .btn-sign-in { margin-top: 7px; text-shadow: none; diff --git a/app/assets/stylesheets/generic/flash.scss b/app/assets/stylesheets/generic/flash.scss new file mode 100644 index 00000000000..95d28aaef6c --- /dev/null +++ b/app/assets/stylesheets/generic/flash.scss @@ -0,0 +1,25 @@ +.flash-container { + display: none; + cursor: pointer; + margin: 0; + text-align: center; + color: #fff; + font-size: 14px; + position: fixed; + bottom: 0; + width: 100%; + opacity: 0.8; + z-index: 100; + + .flash-notice { + background: #49C; + padding: 10px; + text-shadow: 0 1px 1px #178; + } + + .flash-alert { + background: #C67; + text-shadow: 0 1px 1px #945; + padding: 10px; + } +} diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 0e0a365a82d..98062fc0a86 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -48,4 +48,27 @@ margin: 0; } } + + .state-label { + font-size: 14px; + padding: 9px 25px; + text-align: center; + text-shadow: none; + margin-right: 20px; + + &.state-label-blue { + background: #31708f; + color: #FFF; + } + + &.state-label-green { + background: #4A4; + color: #FFF; + } + + &.state-label-red { + background: #DA4E49; + color: #FFF; + } + } } diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss index 7ee1fec4e03..03597c1fc7d 100644 --- a/app/assets/stylesheets/generic/selects.scss +++ b/app/assets/stylesheets/generic/selects.scss @@ -87,3 +87,37 @@ select { padding-top: 4px; } } + +.ajax-users-select { + width: 400px; + + &.input-large { + width: 210px; + } + + &.input-clamp { + max-width: 100%; + } +} + +.user-result { + .user-image { + float: left; + } + .user-name { + } + .user-username { + color: #999; + } +} + +.namespace-result { + .namespace-kind { + color: #AAA; + font-weight: normal; + } + .namespace-path { + margin-left: 10px; + font-weight: bolder; + } +} diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 880387a3483..c06bed3c21e 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -178,3 +178,9 @@ .shadow { @include box-shadow(0 5px 15px #000); } + +.wiki, .note-body { + .highlight { + border: 1px solid #DDD; + } +} diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/main/layout.scss index 4fb01a6fd05..9e009a5e0ad 100644 --- a/app/assets/stylesheets/main/layout.scss +++ b/app/assets/stylesheets/main/layout.scss @@ -16,5 +16,5 @@ body { } .container .content { - margin: 0 0 50px; + margin: 0 0; } diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index 89054cb5b52..f3355c8f1d6 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -10,336 +10,6 @@ } } -.file { - border: 1px solid #CCC; - margin-bottom: 1em; - - .header { - @extend .clearfix; - background: #DDD; - border-bottom: 1px solid #CCC; - padding: 5px 5px 5px 10px; - color: #555; - - > span { - font-family: $monospace_font; - font-size: 14px; - line-height: 2; - } - - .diff-btn-group { - float: right; - - .btn { - background-color: #EEE; - color: #666; - font-weight: bolder; - } - } - - .commit-short-id { - font-family: $monospace_font; - font-size: smaller; - } - - .file-mode { - font-family: $monospace_font; - } - } - .content { - overflow: auto; - overflow-y: hidden; - background: #FFF; - color: #333; - font-size: 12px; - .old { - span.idiff { - background-color: #FAA; - } - } - .new { - span.idiff { - background-color: #AFA; - } - } - - table { - width: 100%; - font-family: $monospace_font; - border: none; - margin: 0px; - padding: 0px; - td { - line-height: 18px; - font-size: 12px; - } - } - .old_line, .new_line, .diff_line { - margin: 0px; - padding: 0px; - border: none; - background: #EEE; - color: #666; - padding: 0px 5px; - border-right: 1px solid #ccc; - text-align: right; - min-width: 35px; - max-width: 50px; - width: 35px; - @include user-select(none); - a { - float: left; - width: 35px; - font-weight: normal; - color: #666; - &:hover { - text-decoration: underline; - } - } - &.new { - background: #CFD; - } - &.old { - background: #FDD; - } - } - .diff_line { - padding: 0; - } - .line_holder { - &.old .old_line, - &.old .new_line { - background: #FCC; - border-color: #E7BABA; - } - &.new .old_line, - &.new .new_line { - background: #CFC; - border-color: #B9ECB9; - } - } - .line_content { - display: block; - white-space: pre; - height: 18px; - margin: 0px; - padding: 0px 0.5em; - border: none; - &.new { - background: #CFD; - } - &.old { - background: #FDD; - } - &.matched { - color: #ccc; - background: #fafafa; - } - &.parallel { - display: table-cell; - overflow: hidden; - width: 50%; - } - } - } - .image { - background: #ddd; - text-align: center; - padding: 30px; - .wrap{ - display: inline-block; - } - - .frame { - display: inline-block; - background-color: #fff; - line-height: 0; - img{ - border: 1px solid #FFF; - background: url('trans_bg.gif'); - max-width: 100%; - } - &.deleted { - border: 1px solid $deleted; - } - - &.added { - border: 1px solid $added; - } - } - .image-info{ - font-size: 12px; - margin: 5px 0 0 0; - color: grey; - } - - .view.swipe{ - position: relative; - - .swipe-frame{ - display: block; - margin: auto; - position: relative; - } - .swipe-wrap{ - overflow: hidden; - border-left: 1px solid #999; - position: absolute; - display: block; - top: 13px; - right: 7px; - } - .frame{ - top: 0; - right: 0; - position: absolute; - &.deleted{ - margin: 0; - display: block; - top: 13px; - right: 7px; - } - } - .swipe-bar{ - display: block; - height: 100%; - width: 15px; - z-index: 100; - position: absolute; - cursor: pointer; - &:hover{ - .top-handle{ - background-position: -15px 3px; - } - .bottom-handle{ - background-position: -15px -11px; - } - }; - .top-handle{ - display: block; - height: 14px; - width: 15px; - position: absolute; - top: 0px; - background: url('swipemode_sprites.gif') 0 3px no-repeat; - } - .bottom-handle{ - display: block; - height: 14px; - width: 15px; - position: absolute; - bottom: 0px; - background: url('swipemode_sprites.gif') 0 -11px no-repeat; - } - } - } //.view.swipe - .view.onion-skin{ - .onion-skin-frame{ - display: block; - margin: auto; - position: relative; - } - .frame.added, .frame.deleted { - position: absolute; - display: block; - top: 0px; - left: 0px; - } - .controls{ - display: block; - height: 14px; - width: 300px; - z-index: 100; - position: absolute; - bottom: 0px; - left: 50%; - margin-left: -150px; - - .drag-track{ - display: block; - position: absolute; - left: 12px; - height: 10px; - width: 276px; - background: url('onion_skin_sprites.gif') -4px -20px repeat-x; - } - - .dragger { - display: block; - position: absolute; - left: 0px; - top: 0px; - height: 14px; - width: 14px; - background: url('onion_skin_sprites.gif') 0px -34px repeat-x; - cursor: pointer; - } - - .transparent { - display: block; - position: absolute; - top: 2px; - right: 0px; - height: 10px; - width: 10px; - background: url('onion_skin_sprites.gif') -2px 0px no-repeat; - } - - .opaque { - display: block; - position: absolute; - top: 2px; - left: 0px; - height: 10px; - width: 10px; - background: url('onion_skin_sprites.gif') -2px -10px no-repeat; - } - } - } //.view.onion-skin - } - .view-modes{ - - padding: 10px; - text-align: center; - - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - - ul, li{ - list-style: none; - margin: 0; - padding: 0; - display: inline-block; - } - - li{ - color: grey; - border-left: 1px solid #c1c1c1; - padding: 0 12px 0 16px; - cursor: pointer; - &:first-child{ - border-left: none; - } - &:hover{ - text-decoration: underline; - } - &.active{ - &:hover{ - text-decoration: none; - } - cursor: default; - color: #333; - } - &.disabled{ - display: none; - } - } - } -} - /** COMMIT BLOCK **/ .commit-title{ display: block; @@ -498,6 +168,25 @@ li.commit { text-decoration: underline; } } + + .text-expander { + background: #eee; + color: #555; + padding: 0 5px; + cursor: pointer; + margin-left: 4px; + &:hover { + background-color: #ddd; + } + } + } + + .commit-row-description { + font-size: 14px; + border-left: 1px solid #e5e5e5; + padding: 0 15px 0 7px; + margin: 5px 0 10px 5px; + display: none; } .commit-row-info { diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss new file mode 100644 index 00000000000..74d144dfb06 --- /dev/null +++ b/app/assets/stylesheets/sections/diff.scss @@ -0,0 +1,329 @@ +.diff-file { + border: 1px solid #CCC; + margin-bottom: 1em; + + .diff-header { + @extend .clearfix; + background: #DDD; + border-bottom: 1px solid #CCC; + padding: 5px 5px 5px 10px; + color: #555; + + > span { + font-family: $monospace_font; + font-size: 14px; + line-height: 2; + } + + .diff-btn-group { + float: right; + + .btn { + background-color: #EEE; + color: #666; + font-weight: bolder; + } + } + + .commit-short-id { + font-family: $monospace_font; + font-size: smaller; + } + + .file-mode { + font-family: $monospace_font; + } + } + .diff-content { + overflow: auto; + overflow-y: hidden; + background: #FFF; + color: #333; + font-size: 12px; + .old { + span.idiff { + background-color: #FAA; + } + } + .new { + span.idiff { + background-color: #AFA; + } + } + + table { + width: 100%; + font-family: $monospace_font; + border: none; + margin: 0px; + padding: 0px; + td { + line-height: 18px; + font-size: 12px; + } + } + .old_line, .new_line, .diff_line { + margin: 0px; + padding: 0px; + border: none; + background: #EEE; + color: #666; + padding: 0px 5px; + border-right: 1px solid #ccc; + text-align: right; + min-width: 35px; + max-width: 50px; + width: 35px; + @include user-select(none); + a { + float: left; + width: 35px; + font-weight: normal; + color: #666; + &:hover { + text-decoration: underline; + } + } + &.new { + background: #CFD; + } + &.old { + background: #FDD; + } + } + .diff_line { + padding: 0; + } + .line_holder { + &.old .old_line, + &.old .new_line { + background: #FCC; + border-color: #E7BABA; + } + &.new .old_line, + &.new .new_line { + background: #CFC; + border-color: #B9ECB9; + } + } + .line_content { + display: block; + white-space: pre; + height: 18px; + margin: 0px; + padding: 0px 0.5em; + border: none; + &.new { + background: #CFD; + } + &.old { + background: #FDD; + } + &.matched { + color: #ccc; + background: #fafafa; + } + &.parallel { + display: table-cell; + overflow: hidden; + width: 50%; + } + } + } + .image { + background: #ddd; + text-align: center; + padding: 30px; + .wrap{ + display: inline-block; + } + + .frame { + display: inline-block; + background-color: #fff; + line-height: 0; + img{ + border: 1px solid #FFF; + background: url('trans_bg.gif'); + max-width: 100%; + } + &.deleted { + border: 1px solid $deleted; + } + + &.added { + border: 1px solid $added; + } + } + .image-info{ + font-size: 12px; + margin: 5px 0 0 0; + color: grey; + } + + .view.swipe{ + position: relative; + + .swipe-frame{ + display: block; + margin: auto; + position: relative; + } + .swipe-wrap{ + overflow: hidden; + border-left: 1px solid #999; + position: absolute; + display: block; + top: 13px; + right: 7px; + } + .frame{ + top: 0; + right: 0; + position: absolute; + &.deleted{ + margin: 0; + display: block; + top: 13px; + right: 7px; + } + } + .swipe-bar{ + display: block; + height: 100%; + width: 15px; + z-index: 100; + position: absolute; + cursor: pointer; + &:hover{ + .top-handle{ + background-position: -15px 3px; + } + .bottom-handle{ + background-position: -15px -11px; + } + }; + .top-handle{ + display: block; + height: 14px; + width: 15px; + position: absolute; + top: 0px; + background: url('swipemode_sprites.gif') 0 3px no-repeat; + } + .bottom-handle{ + display: block; + height: 14px; + width: 15px; + position: absolute; + bottom: 0px; + background: url('swipemode_sprites.gif') 0 -11px no-repeat; + } + } + } //.view.swipe + .view.onion-skin{ + .onion-skin-frame{ + display: block; + margin: auto; + position: relative; + } + .frame.added, .frame.deleted { + position: absolute; + display: block; + top: 0px; + left: 0px; + } + .controls{ + display: block; + height: 14px; + width: 300px; + z-index: 100; + position: absolute; + bottom: 0px; + left: 50%; + margin-left: -150px; + + .drag-track{ + display: block; + position: absolute; + left: 12px; + height: 10px; + width: 276px; + background: url('onion_skin_sprites.gif') -4px -20px repeat-x; + } + + .dragger { + display: block; + position: absolute; + left: 0px; + top: 0px; + height: 14px; + width: 14px; + background: url('onion_skin_sprites.gif') 0px -34px repeat-x; + cursor: pointer; + } + + .transparent { + display: block; + position: absolute; + top: 2px; + right: 0px; + height: 10px; + width: 10px; + background: url('onion_skin_sprites.gif') -2px 0px no-repeat; + } + + .opaque { + display: block; + position: absolute; + top: 2px; + left: 0px; + height: 10px; + width: 10px; + background: url('onion_skin_sprites.gif') -2px -10px no-repeat; + } + } + } //.view.onion-skin + } + .view-modes{ + + padding: 10px; + text-align: center; + + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + + ul, li{ + list-style: none; + margin: 0; + padding: 0; + display: inline-block; + } + + li{ + color: grey; + border-left: 1px solid #c1c1c1; + padding: 0 12px 0 16px; + cursor: pointer; + &:first-child{ + border-left: none; + } + &:hover{ + text-decoration: underline; + } + &.active{ + &:hover{ + text-decoration: none; + } + cursor: default; + color: #333; + } + &.disabled{ + display: none; + } + } + } +} diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index 883c9a859ef..68873327845 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -19,7 +19,7 @@ header { line-height: 32px; padding: 6px 10px; - &:hover { + &:hover, &:focus, &:active { background: none; } } @@ -192,7 +192,8 @@ header { color: #AAA; text-shadow: 0 1px 0 #444; - &:hover { + &:hover, &:focus, &:active { + background: none; color: #FFF; } } diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 4cb8117633b..d4f8c8108ab 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -125,3 +125,21 @@ form.edit-issue { margin: 0; } + +.merge-request, +.issue { + &.today { + background: #EFE; + border-color: #CEC; + } + + &.closed { + background: #F5f5f5; + border-color: #E5E5E5; + } + + &.merged { + background: #F5f5f5; + border-color: #E5E5E5; + } +} diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 9f5f1579fbd..651b39753e3 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -47,13 +47,13 @@ ul.notes { .discussion-body { margin-left: 50px; - .file, + .diff-file, .discussion-hidden, .notes { @extend .borders; background-color: #F9F9F9; } - .file .notes { + .diff-file .notes { /* reset */ background: inherit; border: none; @@ -114,7 +114,7 @@ ul.notes { } } -.file .notes_holder { +.diff-file .notes_holder { font-size: 13px; line-height: 18px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; @@ -184,7 +184,7 @@ ul.notes { } } } -.file .note .note-actions { +.diff-file .note .note-actions { right: 0; top: 0; } @@ -195,7 +195,7 @@ ul.notes { * Line note button on the side of diffs */ -.file tr.line_holder { +.diff-file tr.line_holder { .add-diff-note { background: url("diff_note_add.png") no-repeat left 0; height: 22px; @@ -235,22 +235,25 @@ ul.notes { .reply-btn { @extend .btn-primary; } -.file .content tr.line_holder:hover { - &> td.line_content { - background: $hover !important; - border-color: darken($hover, 10%) !important; +.diff-file .diff-content { + tr.line_holder:hover { + &> td.line_content { + background: $hover !important; + border-color: darken($hover, 10%) !important; + } + &> td.new_line, + &> td.old_line { + background: darken($hover, 4%) !important; + border-color: darken($hover, 10%) !important; + } } - &> td.new_line, - &> td.old_line { - background: darken($hover, 4%) !important; - border-color: darken($hover, 10%) !important; + + tr.line_holder:hover > td .line_note_link { + opacity: 1.0; + filter: alpha(opacity=100); } } -.file .content tr.line_holder:hover > td .line_note_link { - opacity: 1.0; - filter: alpha(opacity=100); -} -.file, +.diff-file, .discussion { .new_note { margin: 0; diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index bdbb9a354b4..5b06af79d5a 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -68,7 +68,9 @@ class Admin::UsersController < Admin::ApplicationController params[:user].delete(:password_confirmation) end - user.admin = (admin && admin.to_i > 0) + if admin.present? + user.admin = !admin.to_i.zero? + end respond_to do |format| if user.update_attributes(params[:user], as: :admin) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index acb2f2c21d8..9ed46c23942 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -154,7 +154,6 @@ class ApplicationController < ActionController::Base end def dev_tools - Rack::MiniProfiler.authorize_request end def default_headers @@ -211,4 +210,8 @@ class ApplicationController < ActionController::Base devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) } devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) } end + + def hexdigest(string) + Digest::SHA1.hexdigest string + end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index b95233c0e91..a74e97ac253 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -53,14 +53,15 @@ class DashboardController < ApplicationController end def merge_requests - @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) + @merge_requests = MergeRequestsFinder.new.execute(current_user, params) @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = @merge_requests.preload(:author, :target_project) end def issues - @issues = FilteringService.new.execute(Issue, current_user, params) + @issues = IssuesFinder.new.execute(current_user, params) @issues = @issues.page(params[:page]).per(20) - @issues = @issues.includes(:author, :project) + @issues = @issues.preload(:author, :project) respond_to do |format| format.html @@ -77,5 +78,6 @@ class DashboardController < ApplicationController def default_filter params[:scope] = 'assigned-to-me' if params[:scope].blank? params[:state] = 'opened' if params[:state].blank? + params[:authorized_only] = true end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index b927dd2f20a..ebddf36de97 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,4 +1,5 @@ class GroupsController < ApplicationController + skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests] respond_to :html before_filter :group, except: [:new, :create] @@ -36,7 +37,7 @@ class GroupsController < ApplicationController @events = Event.in_projects(project_ids) @events = event_filter.apply_filter(@events) @events = @events.limit(20).offset(params[:offset] || 0) - @last_push = current_user.recent_push + @last_push = current_user.recent_push if current_user respond_to do |format| format.html @@ -46,14 +47,15 @@ class GroupsController < ApplicationController end def merge_requests - @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) + @merge_requests = MergeRequestsFinder.new.execute(current_user, params) @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = @merge_requests.preload(:author, :target_project) end def issues - @issues = FilteringService.new.execute(Issue, current_user, params) + @issues = IssuesFinder.new.execute(current_user, params) @issues = @issues.page(params[:page]).per(20) - @issues = @issues.includes(:author, :project) + @issues = @issues.preload(:author, :project) respond_to do |format| format.html @@ -98,17 +100,21 @@ class GroupsController < ApplicationController end def projects - @projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity + @projects ||= ProjectsFinder.new.execute(current_user, group: group) end def project_ids - projects.map(&:id) + projects.pluck(:id) end # Dont allow unauthorized access to group def authorize_read_group! unless @group and (projects.present? or can?(current_user, :read_group, @group)) - return render_404 + if current_user.nil? + return authenticate_user! + else + return render_404 + end end end @@ -131,13 +137,21 @@ class GroupsController < ApplicationController def determine_layout if [:new, :create].include?(action_name.to_sym) 'navless' - else + elsif current_user 'group' + else + 'public_group' end end def default_filter - params[:scope] = 'assigned-to-me' if params[:scope].blank? + if params[:scope].blank? + if current_user + params[:scope] = 'assigned-to-me' + else + params[:scope] = 'all' + end + end params[:state] = 'opened' if params[:state].blank? params[:group_id] = @group.id end diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index c54b757d13c..ff5206b6fa1 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -2,6 +2,8 @@ class Projects::EditTreeController < Projects::BaseTreeController before_filter :require_branch_head before_filter :blob before_filter :authorize_push! + before_filter :from_merge_request + before_filter :after_edit_path def show @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha @@ -13,15 +15,11 @@ class Projects::EditTreeController < Projects::BaseTreeController if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - # If blob edit was initiated from merge request page - from_merge_request = MergeRequest.find_by(id: params[:from_merge_request_id]) - if from_merge_request from_merge_request.reload_code - redirect_to diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) - else - redirect_to project_blob_path(@project, @id) end + + redirect_to after_edit_path else flash[:alert] = result[:error] render :show @@ -33,4 +31,19 @@ class Projects::EditTreeController < Projects::BaseTreeController def blob @blob ||= @repository.blob_at(@commit.id, @path) end + + def after_edit_path + @after_edit_path ||= + if from_merge_request + diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) + + "#file-path-#{hexdigest(@path)}" + else + project_blob_path(@project, @id) + end + end + + def from_merge_request + # If blob edit was initiated from merge request page + @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index dd11948edd1..9d97c820f38 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -121,7 +121,7 @@ class Projects::IssuesController < Projects::ApplicationController def issues_filtered params[:scope] = 'all' if params[:scope].blank? params[:state] = 'opened' if params[:state].blank? - @issues = FilteringService.new.execute(Issue, current_user, params.merge(project_id: @project.id)) + @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id)) end # Since iids are implemented only in 6.1 diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 65f9e2b9d57..0166ca9ff00 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -13,7 +13,7 @@ class Projects::LabelsController < Projects::ApplicationController def generate Gitlab::IssuesLabels.generate(@project) - redirect_to project_labels_path(@project) + redirect_to project_issues_path(@project) end protected diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2b410c5a610..51509ecda35 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -21,7 +21,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController params[:scope] = 'all' if params[:scope].blank? params[:state] = 'opened' if params[:state].blank? - @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params.merge(project_id: @project.id)) + @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id)) @merge_requests = @merge_requests.page(params[:page]).per(20) @sort = params[:sort].humanize @@ -60,7 +60,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController def new @merge_request = MergeRequest.new(params[:merge_request]) @merge_request.source_project = @project unless @merge_request.source_project - @merge_request.target_project = @project unless @merge_request.target_project + @merge_request.target_project ||= (@project.forked_from_project || @project) + @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names + + @merge_request.target_branch ||= @merge_request.target_project.default_branch + @source_project = @merge_request.source_project @merge_request end @@ -162,7 +166,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_status - status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) + status = @merge_request.source_project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) response = {status: status} render json: response diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 9c9c2decc78..85d042a89b5 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -5,7 +5,7 @@ class Projects::NotesController < Projects::ApplicationController before_filter :authorize_admin_note!, only: [:update, :destroy] def index - @notes = Notes::LoadService.new(project, current_user, params).execute + @notes = NotesFinder.new.execute(project, current_user, params) notes_json = { notes: [] } diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb index d7297161c22..d6238f79547 100644 --- a/app/controllers/public/projects_controller.rb +++ b/app/controllers/public/projects_controller.rb @@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController layout 'public' def index - @projects = Project.public_or_internal_only(current_user) + @projects = Project.publicish(current_user) @projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.includes(:namespace).page(params[:page]).per(20) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index e86601a439e..9461174b950 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,15 +1,15 @@ class UsersController < ApplicationController - skip_before_filter :authenticate_user!, only: [:show] layout :determine_layout def show @user = User.find_by_username!(params[:username]) - @projects = @user.authorized_projects.includes(:namespace).select {|project| can?(current_user, :read_project, project)} + @projects = @user.authorized_projects.accessible_to(current_user) if !current_user && @projects.empty? return authenticate_user! end - @events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20) + @groups = @user.groups.accessible_to(current_user) + @events = @user.recent_events.where(project_id: @projects.pluck(:id)).limit(20) @title = @user.name end diff --git a/app/finders/README.md b/app/finders/README.md new file mode 100644 index 00000000000..47823c51efb --- /dev/null +++ b/app/finders/README.md @@ -0,0 +1,22 @@ +# Finders + +This type of classes responsible for collectiong items based on different conditions. +To prevent lookup methods in models like this: + +```ruby +class Project + def issues_for_user_filtered_by(user, filter) + # A lot of logic not related to project model itself + end +end + +issues = project.issues_for_user_filtered_by(user, params) +``` + +Better use this: + +```ruby +issues = IssuesFinder.new.execute(project, user, filter) +``` + +It will help keep models thiner diff --git a/app/services/filtering_service.rb b/app/finders/base_finder.rb index 52537f7ba4f..d20716fb170 100644 --- a/app/services/filtering_service.rb +++ b/app/finders/base_finder.rb @@ -1,4 +1,4 @@ -# FilteringService class +# BaseFinder # # Used to filter Issues and MergeRequests collections by set of params # @@ -16,11 +16,10 @@ # label_name: string # sort: string # -class FilteringService - attr_accessor :klass, :current_user, :params +class BaseFinder + attr_accessor :current_user, :params - def execute(klass, current_user, params) - @klass = klass + def execute(current_user, params) @current_user = current_user @params = params @@ -41,16 +40,16 @@ class FilteringService def init_collection table_name = klass.table_name - return klass.of_projects(Project.public_only) unless current_user - if project - if current_user.can?(:read_project, project) + if project.public? || (current_user && current_user.can?(:read_project, project)) project.send(table_name) else [] end - else + elsif current_user && params[:authorized_only].presence klass.of_projects(current_user.authorized_projects) + else + klass.of_projects(Project.accessible_to(current_user)) end end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb new file mode 100644 index 00000000000..8e0c606249e --- /dev/null +++ b/app/finders/issues_finder.rb @@ -0,0 +1,22 @@ +# Finders::Issues class +# +# Used to filter Issues collections by set of params +# +# Arguments: +# current_user - which user use +# params: +# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# state: 'open' or 'closed' or 'all' +# group_id: integer +# project_id: integer +# milestone_id: integer +# assignee_id: integer +# search: string +# label_name: string +# sort: string +# +class IssuesFinder < BaseFinder + def klass + Issue + end +end diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb new file mode 100644 index 00000000000..3727149c8fb --- /dev/null +++ b/app/finders/merge_requests_finder.rb @@ -0,0 +1,22 @@ +# Finders::MergeRequest class +# +# Used to filter MergeRequests collections by set of params +# +# Arguments: +# current_user - which user use +# params: +# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# state: 'open' or 'closed' or 'all' +# group_id: integer +# project_id: integer +# milestone_id: integer +# assignee_id: integer +# search: string +# label_name: string +# sort: string +# +class MergeRequestsFinder < BaseFinder + def klass + MergeRequest + end +end diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb new file mode 100644 index 00000000000..384316e14b7 --- /dev/null +++ b/app/finders/notes_finder.rb @@ -0,0 +1,17 @@ +class NotesFinder + def execute(project, current_user, params) + target_type = params[:target_type] + target_id = params[:target_id] + + case target_type + when "commit" + project.notes.for_commit_id(target_id).not_inline.fresh + when "issue" + project.issues.find(target_id).notes.inc_author.fresh + when "merge_request" + project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh + when "snippet" + project.snippets.find(target_id).notes.fresh + end + end +end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb new file mode 100644 index 00000000000..bfaba758788 --- /dev/null +++ b/app/finders/projects_finder.rb @@ -0,0 +1,63 @@ +class ProjectsFinder + def execute(current_user, options) + group = options[:group] + + if group + group_projects(current_user, group) + else + all_projects(current_user) + end + end + + private + + def group_projects(current_user, group) + if current_user + if group.users.include?(current_user) + # User is group member + # + # Return ALL group projects + group.projects + else + projects_members = UsersProject.where( + project_id: group.projects, + user_id: current_user + ) + + if projects_members.any? + # User is a project member + # + # Return only: + # public projects + # internal projects + # joined projects + # + group.projects.where( + "projects.id IN (?) OR projects.visibility_level IN (?)", + projects_members.pluck(:project_id), + Project.public_and_internal_levels + ) + else + # User has no access to group or group projects + # + # Return only: + # public projects + # internal projects + # + group.projects.public_and_internal_only + end + end + else + # Not authenticated + # + # Return only: + # public projects + group.projects.public_only + end + end + + def all_projects + # TODO: implement + raise 'Not implemented yet' + end +end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index b25662e3ba2..6cad5e4658e 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -124,12 +124,14 @@ module GitlabMarkdownHelper end def rebuild_path(path_with_namespace, path, requested_path, ref) + path.gsub!(/(#.*)/, "") + id = $1 || "" file_path = relative_file_path(path, requested_path) [ path_with_namespace, path_with_ref(file_path, ref), file_path - ].compact.join("/") + ].compact.join("/").gsub(/\/*$/, '') + id end # Checks if the path exists in the repo @@ -154,6 +156,7 @@ module GitlabMarkdownHelper # If we are at doc/api and the README.md shown in below the tree view # this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md def build_nested_path(path, request_path) + return request_path if path == "" return path unless request_path if local_path(request_path) == "tree" base = request_path.split("/").push(path) @@ -166,7 +169,7 @@ module GitlabMarkdownHelper end def file_exists?(path) - return false if path.nil? || path.empty? + return false if path.nil? return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any? end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 5865396b698..cfc9a572cac 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -6,6 +6,14 @@ module GroupsHelper def leave_group_message(group) "Are you sure you want to leave \"#{group}\" group?" end + + def should_user_see_group_roles?(user, group) + if user + user.is_admin? || group.members.exists?(user_id: user.id) + else + false + end + end def group_head_title title = @group.name diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 62f061bb079..b8285d43302 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -1,8 +1,9 @@ module MergeRequestsHelper def new_mr_path_from_push_event(event) + target_project = event.project.forked_from_project || event.project new_project_merge_request_path( event.project, - new_mr_from_push_event(event, event.project) + new_mr_from_push_event(event, target_project) ) end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 470a495f036..d7f3da7e537 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -4,8 +4,7 @@ module SearchHelper resources_results = [ groups_autocomplete(term), - projects_autocomplete(term), - public_projects_autocomplete(term), + projects_autocomplete(term) ].flatten generic_results = project_autocomplete + default_autocomplete + help_autocomplete @@ -82,17 +81,7 @@ module SearchHelper # Autocomplete results for the current user's projects def projects_autocomplete(term, limit = 5) - current_user.authorized_projects.search_by_title(term).non_archived.limit(limit).map do |p| - { - label: "project: #{search_result_sanitize(p.name_with_namespace)}", - url: project_path(p) - } - end - end - - # Autocomplete results for the current user's projects - def public_projects_autocomplete(term, limit = 5) - Project.public_or_internal_only(current_user).search_by_title(term).non_archived.limit(limit).map do |p| + Project.accessible_to(current_user).search_by_title(term).non_archived.limit(limit).map do |p| { label: "project: #{search_result_sanitize(p.name_with_namespace)}", url: project_path(p) diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index b2b4b83d6c3..3adb47dc5b1 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -3,22 +3,27 @@ module Emails def new_issue_email(recipient_id, issue_id) @issue = Issue.find(issue_id) @project = @issue.project - mail(to: recipient(recipient_id), subject: subject("New issue ##{@issue.iid}", @issue.title)) + mail(from: sender(@issue.author_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end - def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) + def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) @issue = Issue.find(issue_id) @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @issue.project - mail(to: recipient(recipient_id), subject: subject("Changed issue ##{@issue.iid}", @issue.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @issue = Issue.find issue_id @project = @issue.project @updated_by = User.find updated_by_user_id - mail(to: recipient(recipient_id), - subject: subject("Closed issue ##{@issue.iid}", @issue.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @@ -26,8 +31,9 @@ module Emails @issue_status = status @project = @issue.project @updated_by = User.find updated_by_user_id - mail(to: recipient(recipient_id), - subject: subject("Changed issue ##{@issue.iid}", @issue.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index e60887d525a..0845e14edc7 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -3,27 +3,35 @@ module Emails def new_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title)) + mail(from: sender(@merge_request.author_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end - def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) + def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) @merge_request = MergeRequest.find(merge_request_id) @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @merge_request.project - mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @merge_request = MergeRequest.find(merge_request_id) @updated_by = User.find updated_by_user_id @project = @merge_request.project - mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title)) + mail(from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end def merged_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title)) + mail(from: sender(@merge_request.author_id_of_changes), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end end @@ -57,7 +65,7 @@ module Emails # >> subject('Lorem ipsum', 'Dolor sit amet') # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet" def subject(*extra) - subject = "GitLab Merge Request |" + subject = "Merge Request | " if @merge_request.for_fork? subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}" else diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index e967cf6dc73..00b127da429 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,27 +4,35 @@ module Emails @note = Note.find(note_id) @commit = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("Note for commit #{@commit.short_id}", @commit.title)) + mail(from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@commit.title} (#{@commit.short_id})")) end def note_issue_email(recipient_id, note_id) @note = Note.find(note_id) @issue = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("Note for issue ##{@issue.iid}")) + mail(from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def note_merge_request_email(recipient_id, note_id) @note = Note.find(note_id) @merge_request = @note.noteable @project = @note.project - mail(to: recipient(recipient_id), subject: subject("Note for merge request ##{@merge_request.iid}")) + mail(from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) end def note_wall_email(recipient_id, note_id) @note = Note.find(note_id) @project = @note.project - mail(to: recipient(recipient_id), subject: subject("Note on wall")) + mail(from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("Note on wall")) end end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 428d74d83c6..46f24e9fb7c 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -22,7 +22,9 @@ module Emails @diffs = compare.diffs @branch = branch - mail(to: recipient, subject: subject("New push to repository")) + mail(from: sender(author_id), + to: recipient, + subject: subject("New push to repository")) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 6c1a3328960..554f53cf148 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -15,16 +15,33 @@ class Notify < ActionMailer::Base default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root - default from: Gitlab.config.gitlab.email_from + default from: Proc.new { default_sender_address.format } default reply_to: "noreply@#{Gitlab.config.gitlab.host}" - # Just send email with 3 seconds delay + # Just send email with 2 seconds delay def self.delay delay_for(2.seconds) 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 + 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) + if sender = User.find(sender_id) + address = default_sender_address + address.display_name = sender.name + address.format + end + end + # Look up a User by their ID and return their email address # # recipient_id - User ID @@ -43,21 +60,21 @@ class Notify < ActionMailer::Base # Examples # # >> subject('Lorem ipsum') - # => "GitLab | Lorem ipsum" + # => "Lorem ipsum" # # # Automatically inserts Project name when @project is set # >> @project = Project.last # => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...> # >> subject('Lorem ipsum') - # => "GitLab | Ruby on Rails | Lorem ipsum " + # => "Ruby on Rails | Lorem ipsum " # # # Accepts multiple arguments # >> subject('Lorem ipsum', 'Dolor sit amet') - # => "GitLab | Lorem ipsum | Dolor sit amet" + # => "Lorem ipsum | Dolor sit amet" def subject(*extra) - subject = "GitLab" - subject << (@project ? " | #{@project.name_with_namespace}" : "") - subject << " | " + extra.join(' | ') if extra.present? + subject = "" + subject << "#{@project.name} | " if @project + subject << extra.join(' | ') if extra.present? subject end end diff --git a/app/models/ability.rb b/app/models/ability.rb index ba0ce527f64..69ada753d02 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -43,7 +43,19 @@ class Ability :download_code ] else - [] + group = if subject.kind_of?(Group) + subject + elsif subject.respond_to?(:group) + subject.group + else + nil + end + + if group && group.has_projects_accessible_to?(nil) + [:read_group] + else + [] + end end end @@ -172,7 +184,7 @@ class Ability def group_abilities user, group rules = [] - if group.users.include?(user) || user.admin? + if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any? rules << :read_group end diff --git a/app/models/commit.rb b/app/models/commit.rb index bcc1bcbd96a..c313aeb7572 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -99,14 +99,16 @@ class Commit # # cut off, ellipses (`&hellp;`) are prepended to the commit message. def description - description = safe_message + title_end = safe_message.index(/\n/) + @description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100) + "…".html_safe << safe_message[80..-1] + else + safe_message.split(/\n/, 2)[1].try(:chomp) + end + end - title_end = description.index(/\n/) - if (!title_end && description.length > 100) || (title_end && title_end > 100) - "…".html_safe << description[80..-1] - else - description.split(/\n/, 2)[1].try(:chomp) - end + def description? + description.present? end # Regular expression that identifies commit message clauses that trigger issue closing. diff --git a/app/models/event.rb b/app/models/event.rb index ddb863c1be2..d43d6eb682f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -56,11 +56,13 @@ class Event < ActiveRecord::Base end def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads') + commit = project.repository.commit(ref.target) + if action.to_s == 'add' before = '00000000' - after = ref.commit.id + after = commit.id else - before = ref.commit.id + before = commit.id after = '00000000' end diff --git a/app/models/group.rb b/app/models/group.rb index 8de0c78c158..0d4d5f4e836 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -25,6 +25,12 @@ class Group < Namespace validates :avatar, file_size: { maximum: 100.kilobytes.to_i } mount_uploader :avatar, AttachmentUploader + + def self.accessible_to(user) + accessible_ids = Project.accessible_to(user).pluck(:namespace_id) + accessible_ids += user.groups.pluck(:id) if user + where(id: accessible_ids) + end def human_name name diff --git a/app/models/key.rb b/app/models/key.rb index 79f7bbd2590..29a76f53f3d 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -53,7 +53,7 @@ class Key < ActiveRecord::Base Tempfile.open('gitlab_key_file') do |file| file.puts key file.rewind - cmd_output, cmd_status = popen("ssh-keygen -lf #{file.path}", '/tmp') + cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp') end if cmd_status.zero? diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 0bc5e1862eb..468c93bd426 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -47,6 +47,14 @@ class Namespace < ActiveRecord::Base def self.global_id 'GLN' end + + def projects_accessible_to(user) + projects.accessible_to(user) + end + + def has_projects_accessible_to?(user) + projects_accessible_to(user).present? + end def to_param path diff --git a/app/models/project.rb b/app/models/project.rb index 2c926ff8a9a..c3bf7a5e435 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -115,18 +115,35 @@ class Project < ActiveRecord::Base scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } - scope :public_only, -> { where(visibility_level: PUBLIC) } - scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } + + scope :public_only, -> { where(visibility_level: Project::PUBLIC) } + scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :non_archived, -> { where(archived: false) } enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab class << self + def public_and_internal_levels + [Project::PUBLIC, Project::INTERNAL] + end + def abandoned where('projects.last_activity_at < ?', 6.months.ago) end + def publicish(user) + visibility_levels = [Project::PUBLIC] + visibility_levels += [Project::INTERNAL] if user + where(visibility_level: visibility_levels) + end + + def accessible_to(user) + accessible_ids = publicish(user).pluck(:id) + accessible_ids += user.authorized_projects.pluck(:id) if user + where(id: accessible_ids) + end + def with_push includes(:events).where('events.action = ?', Event::PUSHED) end diff --git a/app/models/repository.rb b/app/models/repository.rb index 009f5edd1df..35ec84f1651 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -203,7 +203,7 @@ class Repository # # => git@localhost:rack.git # def submodule_url_for(ref, path) - if submodules.any? + if submodules(ref).any? submodule = submodules(ref)[path] if submodule diff --git a/app/models/service.rb b/app/models/service.rb index 26060d00b02..f7e440dcc81 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -19,6 +19,8 @@ # To add new service you should build a class inherited from Service # and implement a set of methods class Service < ActiveRecord::Base + default_value_for :active, false + attr_accessible :title, :token, :type, :active, :api_key belongs_to :project diff --git a/app/services/notes/load_service.rb b/app/services/notes/load_service.rb deleted file mode 100644 index f7ad7d60a3a..00000000000 --- a/app/services/notes/load_service.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Notes - class LoadService < BaseService - def execute - target_type = params[:target_type] - target_id = params[:target_id] - - - @notes = case target_type - when "commit" - project.notes.for_commit_id(target_id).not_inline.fresh - when "issue" - project.issues.find(target_id).notes.inc_author.fresh - when "merge_request" - project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh - when "snippet" - project.snippets.find(target_id).notes.fresh - end - end - end -end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 9d7bb9639ac..5daf573630d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -257,7 +257,7 @@ class NotificationService recipients.delete(current_user) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, target.assignee_id_was) + mailer.send(method, recipient.id, target.id, target.assignee_id_was, current_user.id) end end diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index c1130401578..09c7cb25dd5 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -11,12 +11,8 @@ module Search query = Shellwords.shellescape(query) if query.present? return result unless query.present? - authorized_projects_ids = [] - authorized_projects_ids += current_user.authorized_projects.pluck(:id) if current_user - authorized_projects_ids += Project.public_or_internal_only(current_user).pluck(:id) - group = Group.find_by(id: params[:group_id]) if params[:group_id].present? - projects = Project.where(id: authorized_projects_ids) + projects = Project.accessible_to(current_user) projects = projects.where(namespace_id: group.id) if group projects = projects.search(query) project_ids = projects.pluck(:id) diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 940a539d5e1..5f19d21f106 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -53,5 +53,5 @@ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Destroy', [project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-small btn-remove" - if @projects.blank? - %p.nothing_here_message 0 projects matches + .nothing-here-block 0 projects matches = paginate @projects, theme: "gitlab" diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index e888358dc63..d66119e2712 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -7,7 +7,7 @@ %span.cred (Admin) .pull-right - = link_to edit_admin_user_path(@user), class: "btn grouped" do + = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do %i.icon-edit Edit %hr diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index 39dd600dba3..fdf96dd6f56 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -4,6 +4,6 @@ - if @events.any? .content_list - else - %p.nothing_here_message Projects activity will be displayed here + .nothing-here-block Projects activity will be displayed here = spinner diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml index 2ff1c33c53a..a6bc946bedc 100644 --- a/app/views/dashboard/_groups.html.haml +++ b/app/views/dashboard/_groups.html.haml @@ -17,4 +17,4 @@ %i.icon-angle-right - if groups.blank? %li - %h3.nothing_here_message You have no groups yet. + .nothing-here-block You have no groups yet. diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 8313cc07b5e..44c7a4b8c80 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -14,7 +14,7 @@ - if projects.blank? %li - %h3.nothing_here_message There are no projects here. + .nothing-here-block There are no projects here. - if @projects_count > @projects_limit %li.bottom %span.light diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 8feef97c732..e1c9a5941e9 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -67,7 +67,7 @@ - if @projects.blank? %li - %h3.nothing_here_message There are no projects here. + .nothing-here-block There are no projects here. .bottom = paginate @projects, theme: "gitlab" diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml index 9fbc6c190cc..fe8c0669c0e 100644 --- a/app/views/groups/_filter.html.haml +++ b/app/views/groups/_filter.html.haml @@ -21,7 +21,7 @@ = project.name_with_namespace %small.pull-right= entities_per_project(project, entity) - if @projects.blank? - %p.nothing_here_message This group has no projects yet + .nothing-here-block This group has no projects yet %fieldset %hr diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 029d6cb0efa..bd4e3156af0 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -8,7 +8,7 @@ New project %ul.well-list - if projects.blank? - %p.nothing_here_message This groups has no projects yet + .nothing-here-block This groups has no projects yet - projects.each do |project| %li.project-row = link_to project_path(project), class: dom_class(project) do diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index f14fe923402..9308bd8124e 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -80,7 +80,7 @@ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove" - if @group.projects.blank? - %p.nothing_here_message This group has no projects yet + .nothing-here-block This group has no projects yet .tab-pane#tab-remove .ui-box.ui-box-danger diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index e24df310910..4b11d91dc98 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -5,7 +5,9 @@ %p.light Only issues from %strong #{@group.name} - group are listed here. To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. + group are listed here. + - if current_user + To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. %hr .row diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml index 38069feb37b..0c972622f88 100644 --- a/app/views/groups/members.html.haml +++ b/app/views/groups/members.html.haml @@ -1,9 +1,11 @@ +- show_roles = should_user_see_group_roles?(current_user, @group) %h3.page-title Group members -%p.light - Members of group have access to all group projects. - Read more about permissions - %strong= link_to "here", help_permissions_path, class: "vlink" +- if show_roles + %p.light + Members of group have access to all group projects. + Read more about permissions + %strong= link_to "here", help_permissions_path, class: "vlink" %hr @@ -13,7 +15,7 @@ = search_field_tag :search, params[:search], { placeholder: 'Find member by name', class: 'form-control search-text-input input-mn-300' } = submit_tag 'Search', class: 'btn' - - if current_user.can? :manage_group, @group + - if current_user && current_user.can?(:manage_group, @group) .pull-right = link_to '#', class: 'btn btn-new js-toggle-visibility-link' do Add members @@ -30,7 +32,7 @@ (#{@members.total_count}) %ul.well-list - @members.each do |member| - = render 'users_groups/users_group', member: member, show_controls: true + = render 'users_groups/users_group', member: member, show_roles: show_roles, show_controls: true = paginate @members, theme: 'gitlab' :coffeescript diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index eaf85bbdbc8..209130ec444 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -5,7 +5,9 @@ %p.light Only merge requests from %strong #{@group.name} - group are listed here. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. + group are listed here. + - if current_user + To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. %hr .row .col-md-3 diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 6256c047929..0343670c203 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,16 +1,17 @@ .dashboard .activities.col-md-8.hidden-sm - = render "events/event_last_push", event: @last_push - = link_to dashboard_path, class: 'btn btn-tiny' do - ← To dashboard - + - if current_user + = render "events/event_last_push", event: @last_push + = link_to dashboard_path, class: 'btn btn-tiny' do + ← To dashboard + %span.cgray You will only see events from projects in this group %hr = render 'shared/event_filter' - if @events.any? .content_list - else - %p.nothing_here_message Project activity will be displayed here + .nothing-here-block Project activity will be displayed here = spinner .side.col-md-4 .light-well.append-bottom-20 @@ -21,11 +22,12 @@ - if @group.description.present? %p= @group.description = render "projects", projects: @projects - .prepend-top-20 - = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do - %strong - %i.icon-rss - News Feed + - if current_user + .prepend-top-20 + = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do + %strong + %i.icon-rss + News Feed %hr = render 'shared/promo' diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 5723250151a..9ba20f1347d 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -12,6 +12,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') + = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') -# Atom feed - if current_user diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml new file mode 100644 index 00000000000..135e8daca26 --- /dev/null +++ b/app/views/layouts/_piwik.html.haml @@ -0,0 +1,12 @@ +:javascript + var _paq = _paq || []; + _paq.push(["trackPageView"]); + _paq.push(["enableLinkTracking"]); + + (function() { + var u=(("https:" == document.location.protocol) ? "https" : "http") + "://#{extra_config.piwik_url}/"; + _paq.push(["setTrackerUrl", u+"piwik.php"]); + _paq.push(["setSiteId", "#{extra_config.piwik_site_id}"]); + var d=document, g=d.createElement("script"), s=d.getElementsByTagName("script")[0]; g.type="text/javascript"; + g.defer=true; g.async=true; g.src=u+"piwik.js"; s.parentNode.insertBefore(g,s); + })(); diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index a8bb3b91559..36b102dc25a 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -5,11 +5,13 @@ = nav_link(path: 'groups#issues') do = link_to issues_group_path(@group) do Issues - %span.count= current_user.assigned_issues.opened.of_group(@group).count + - if current_user + %span.count= current_user.assigned_issues.opened.of_group(@group).count = nav_link(path: 'groups#merge_requests') do = link_to merge_requests_group_path(@group) do Merge Requests - %span.count= current_user.cared_merge_requests.opened.of_group(@group).count + - if current_user + %span.count= current_user.cared_merge_requests.opened.of_group(@group).count = nav_link(path: 'groups#members') do = link_to "Members", members_group_path(@group) diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml new file mode 100644 index 00000000000..a289b784725 --- /dev/null +++ b/app/views/layouts/public_group.html.haml @@ -0,0 +1,10 @@ +!!! 5 +%html{ lang: "en"} + = render "layouts/head", title: group_head_title + %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/broadcast" + = render "layouts/public_head_panel", title: "group: #{@group.name}" + %nav.main-nav.navbar-collapse.collapse + .container= render 'layouts/nav/group' + .container + .content= yield diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml index 5fcf9f99e75..cf4ca9c7a84 100644 --- a/app/views/layouts/public_projects.html.haml +++ b/app/views/layouts/public_projects.html.haml @@ -3,7 +3,7 @@ = render "layouts/head", title: @project.name_with_namespace %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" - = render "layouts/public_head_panel", title: @project.name_with_namespace + = render "layouts/public_head_panel", title: project_title(@project) %nav.main-nav .container= render 'layouts/nav/project' .container diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml index eefebf98c51..cebc0b6bccb 100644 --- a/app/views/profiles/groups/index.html.haml +++ b/app/views/profiles/groups/index.html.haml @@ -18,12 +18,12 @@ %li .pull-right - if can?(current_user, :manage_group, group) - = link_to edit_group_path(group), class: "btn-small btn grouped" do + = link_to edit_group_path(group), class: "btn-small btn btn-grouped" do %i.icon-cogs 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 grouped", title: 'Remove user from group' do + = 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 %i.icon-signout Leave diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 79fdb164089..71a4ca91d42 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -17,6 +17,6 @@ = render @keys - if @keys.blank? %li - %h3.nothing_here_message There are no SSH keys with access to your account. + .nothing-here-block There are no SSH keys with access to your account. diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index 080a39ab944..7cbea7c3eb6 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -10,4 +10,4 @@ - unless blob.empty? = render 'shared/file_hljs', blob: blob - else - %p.nothing_here_message Empty file + .nothing-here-block Empty file diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 902dfcea314..f0731977098 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -10,14 +10,14 @@ %i.icon-lock .pull-right - if can?(current_user, :download_code, @project) - = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'grouped btn-group-small' + = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-small' - if branch.name != @repository.root_ref - = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn grouped btn-small', method: :post, title: "Compare" do + = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-small', method: :post, title: "Compare" do %i.icon-copy Compare - if can?(current_user, :admin_project, @project) && branch.name != @repository.root_ref - = link_to project_branch_path(@project, branch.name), class: 'btn grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do + = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do %i.icon-trash - if commit diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 690df98a2ab..bee04eb013e 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -4,7 +4,7 @@ = render "filter" .col-md-9 - unless @branches.empty? - %ul.bordered-list.top-list + %ul.bordered-list.top-list.all-branches - @branches.each do |branch| = render "projects/branches/branch", branch: branch = paginate @branches, theme: 'gitlab' diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 3d666807cf9..3813471831c 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -12,7 +12,7 @@ %ul.dropdown-menu %li= link_to "Email Patches", project_commit_path(@project, @commit, format: :patch) %li= link_to "Plain Diff", project_commit_path(@project, @commit, format: :diff) - = link_to project_tree_path(@project, @commit), class: "btn btn-primary grouped" do + = link_to project_tree_path(@project, @commit), class: "btn btn-primary btn-grouped" do %span Browse Code » %div diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 9772d3ef2ef..a0606662807 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -1,9 +1,12 @@ -%li.commit +%li.commit.js-toggle-container .commit-row-title = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" %span.str-truncated = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" + - if commit.description? + %a.text-expander.js-toggle-button ... + = link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right" .notes_count - notes = project.notes.for_commit_id(commit.id) @@ -12,6 +15,10 @@ %i.icon-comment = notes.count + - if commit.description? + .commit-row-description.js-toggle-content + = simple_format(commit.description) + .commit-row-info = commit_author_link(commit, avatar: true, size: 16) .committed_ago diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml index 0fd04f32ba8..dd287fcc153 100644 --- a/app/views/projects/commits/_diffs.html.haml +++ b/app/views/projects/commits/_diffs.html.haml @@ -8,18 +8,18 @@ - if current_controller?(:commit) or current_controller?(:merge_requests) Please, download the diff as - if current_controller?(:commit) - = link_to "plain diff", project_commit_path(@project, @commit, format: :diff), class: "underlined_link" + = link_to "plain diff", project_commit_path(@project, @commit, format: :diff), class: "underlined-link" or - = link_to "email patch", project_commit_path(@project, @commit, format: :patch), class: "underlined_link" + = link_to "email patch", project_commit_path(@project, @commit, format: :patch), class: "underlined-link" - else - = link_to "plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "underlined_link" + = link_to "plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "underlined-link" or - = link_to "email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "underlined_link" + = link_to "email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "underlined-link" instead. - unless @force_suppress_diff %p If you still want to see the diff - = link_to "click this link", url_for(force_show_diff: true), class: "underlined_link" + = link_to "click this link", url_for(force_show_diff: true), class: "underlined-link" %p.commit-stat-summary Showing @@ -44,8 +44,8 @@ - file = project.repository.blob_at(@commit.id, diff.new_path) - file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file - next unless file - .file{id: "diff-#{i}"} - .header + .diff-file{id: "diff-#{i}"} + .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"} - if diff.deleted_file %span= diff.old_path @@ -70,7 +70,7 @@ %span.commit-short-id= @commit.short_id(6) - .content + .diff-content -# Skipp all non non-supported blobs - next unless file.respond_to?('text?') - if file.text? @@ -82,4 +82,4 @@ - old_file = project.repository.blob_at(@commit.parent_id, diff.old_path) if @commit.parent_id = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i - else - %p.nothing_here_message No preview for this file type + .nothing-here-block No preview for this file type diff --git a/app/views/projects/commits/_image.html.haml b/app/views/projects/commits/_image.html.haml index 9a8b7c857e5..6d9ef5964d9 100644 --- a/app/views/projects/commits/_image.html.haml +++ b/app/views/projects/commits/_image.html.haml @@ -15,7 +15,7 @@ %span.meta-filesize= "#{number_to_human_size old_file.size}" | %b W: - %span.meta-width + %span.meta-width | %b H: %span.meta-height @@ -27,7 +27,7 @@ %span.meta-filesize= "#{number_to_human_size file.size}" | %b W: - %span.meta-width + %span.meta-width | %b H: %span.meta-height @@ -49,7 +49,7 @@ %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} .frame.added %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} - .col-sm-10 + .controls .transparent .drag-track .dragger{:style => "left: 0px;"} diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index 90d86102aca..f50aeba337a 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -20,7 +20,7 @@ = render @enabled_keys - if @enabled_keys.blank? .light-well - %p.nothing_here_message Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one + .nothing-here-block Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one .col-md-6.available-keys %h5 %strong Deploy keys @@ -29,4 +29,4 @@ = render @available_keys - if @available_keys.blank? .light-well - %p.nothing_here_message Deploy keys from projects you have access to will be displayed here + .nothing-here-block Deploy keys from projects you have access to will be displayed here diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index b9cd5a20d50..005d994806e 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -93,10 +93,11 @@ - %center.light.prepend-top-20.padded + .centered-light-block %h3 %i.icon-warning-sign Dangerous settings + %p Project settings below may result in data loss! = link_to '#', class: 'btn js-toggle-visibility-link' do Show it to me @@ -132,7 +133,7 @@ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, method: :post, class: "btn btn-remove" - else - %p.nothing_here_message Only the project owner can archive a project + .nothing-here-block Only the project owner can archive a project - if can?(current_user, :change_namespace, @project) .ui-box.ui-box-danger @@ -153,7 +154,7 @@ .form-actions = f.submit 'Transfer', class: "btn btn-remove" - else - %p.nothing_here_message Only the project owner can transfer a project + .nothing-here-block Only the project owner can transfer a project .ui-box.ui-box-danger .title Rename repository @@ -185,7 +186,7 @@ = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove" - else - %p.nothing_here_message Only project owner can remove a project + .nothing-here-block Only project owner can remove a project .save-project-loader.hide %center diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml index 96c00ab3661..3f2e98f3a7f 100644 --- a/app/views/projects/edit_tree/show.html.haml +++ b/app/views/projects/edit_tree/show.html.haml @@ -11,7 +11,7 @@ %strong= @ref %span.options .btn-group.tree-btn-group - = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } + = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } .file-content.code %pre#editor= @blob.data @@ -29,7 +29,7 @@ .message to branch %strong= @ref - = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message} + = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message} :javascript ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace") diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index a095fd06d2f..00e5ae27779 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -51,8 +51,8 @@ - @hooks.each do |hook| %li .pull-right - = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small grouped" - = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small grouped" + = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small btn-grouped" + = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small btn-grouped" .clearfix %span.monospace= hook.url %p diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 1bd93b774f1..3fc04c26cf2 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -38,10 +38,10 @@ .issue-actions - if can? current_user, :modify_issue, issue - if issue.closed? - = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true + = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue", remote: true - else - = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true - = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do + = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue", remote: true + = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do %i.icon-edit Edit diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 2d69af0b2d1..3b5e398c327 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -66,7 +66,7 @@ = render @issues - if @issues.blank? %li - %h4.nothing_here_message No issues to show + .nothing-here-block No issues to show - if @issues.present? .pull-right diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 7aa37eda965..dcdad7e05cb 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -3,16 +3,16 @@ %span.pull-right - if can?(current_user, :write_issue, @project) - = link_to new_project_issue_path(@project), class: "btn grouped", title: "New Issue", id: "new_issue_link" do + = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do %i.icon-plus New Issue - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped btn-reopen" + = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" - else - = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped btn-close", title: "Close Issue" + = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" - = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do + = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do %i.icon-edit Edit @@ -55,9 +55,9 @@ - content_for :note_actions do - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped btn-reopen" + = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" - else - = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped btn-close", title: "Close Issue" + = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" .participants %cite.cgray #{@issue.participants.count} participants diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index a058d09a1ce..329cf9ceba8 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -7,4 +7,4 @@ - else .light-well - %h3.nothing_here_message Add first label to your issues or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels + .nothing-here-block Add first label to your issues or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 6d96b405e74..51fa29ddcbe 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -29,7 +29,7 @@ .clearfix .pull-left - projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project] - = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace'), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? }) + = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? }) .pull-left = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'}) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index ec9249be00d..c82afeb10c1 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -25,7 +25,7 @@ - content_for :note_actions do - if can?(current_user, :modify_merge_request, @merge_request) - unless @merge_request.closed? - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" + = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index d45bec3a239..34faebf619c 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -65,7 +65,7 @@ = render @merge_requests - if @merge_requests.blank? %li - %h4.nothing_here_message No merge requests to show + .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 diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index 8ca1326c96a..9b4271bbffc 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -29,7 +29,7 @@ = render "projects/commits/commit", commit: commit, project: @merge_request.source_project - else - %h4.nothing_here_message + .nothing-here-block Nothing to merge from %span.label-branch #{@merge_request.source_branch} to diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 2917accdb5d..7c4f43d2d80 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,7 +1,7 @@ - if @merge_request_diff.collected? = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project - elsif @merge_request_diff.empty? - %h4.nothing_here_message Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} + .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else .bs-callout.bs-callout-warning %h4 diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index b27522e46a4..58368ab18b6 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -4,8 +4,8 @@ %span.pull-right - if can?(current_user, :modify_merge_request, @merge_request) - if @merge_request.opened? - .left.btn-group - %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} } + .btn-group.pull-left + %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} } %i.icon-download-alt Download as %span.caret @@ -13,9 +13,9 @@ %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch) %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff) - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" + = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" - = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do + = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do %i.icon-edit Edit diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 4dec3838d97..5579659d60e 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -1,7 +1,7 @@ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) } .pull-right - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do + = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do %i.icon-edit Edit = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-remove" diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 6cfe4d28778..3537650ad43 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -26,6 +26,6 @@ - if @milestones.blank? %li - %h3.nothing_here_message No milestones to show + .nothing-here-block No milestones to show = paginate @milestones, theme: "gitlab" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 31eb5765fab..1487269a8d9 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -3,21 +3,15 @@ Milestone ##{@milestone.iid} %small = @milestone.expires_at - - if @milestone.closed? - %span.state-label.state-label-red Closed - - elsif @milestone.expired? - %span.state-label.state-label-red Expired - - else - %span.state-label.state-label-green Open .pull-right - if can?(current_user, :admin_milestone, @project) - = link_to edit_project_milestone_path(@project, @milestone), class: "btn grouped" do + = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do %i.icon-edit Edit - if @milestone.active? - = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove grouped" + = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove btn-grouped" - else - = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn grouped" + = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped" - if @milestone.issues.any? && @milestone.can_be_closed? .alert.alert-success @@ -29,6 +23,14 @@ .issue-box + .state + - if @milestone.closed? + %span.state-label.state-label-red Closed + - elsif @milestone.expired? + %span.state-label.state-label-red Expired + - else + %span.state-label.state-label-green Open + %h4.title = gfm escape_once(@milestone.title) @@ -63,10 +65,10 @@ %span.badge= @users.count .pull-right - = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do + = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-grouped", title: "New Issue" do %i.icon-plus New Issue - = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link grouped" + = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index ee65ae1e2f5..9b1f4d77587 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -36,21 +36,17 @@ .discussion-body - if note.for_diff_line? - if note.active? - .content - .file= render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note + = render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note - else = link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion' %div.hide.outdated-discussion - .content - .notes{ rel: discussion_notes.first.discussion_id } - = render discussion_notes - + .notes{ rel: discussion_notes.first.discussion_id } + = render discussion_notes - else - .content - .notes{ rel: discussion_notes.first.discussion_id } - = render discussion_notes - = render "projects/notes/discussion_reply_button", note: discussion_notes.first + .notes{ rel: discussion_notes.first.discussion_id } + = render discussion_notes + = render "projects/notes/discussion_reply_button", note: discussion_notes.first -# will be shown when the other one is hidden .discussion-hidden.content.hide @@ -59,4 +55,3 @@ = link_to "javascript:;", class: "js-details-target js-toggler-target" do %i.icon-eye-open Show - diff --git a/app/views/projects/notes/_discussion_diff.html.haml b/app/views/projects/notes/_discussion_diff.html.haml index c3f41a1b6b5..687fac04973 100644 --- a/app/views/projects/notes/_discussion_diff.html.haml +++ b/app/views/projects/notes/_discussion_diff.html.haml @@ -1,24 +1,25 @@ - diff = note.diff -.header - - if diff.deleted_file - %span= diff.old_path - - else - %span= diff.new_path - - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode - %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" - %br/ -.content - %table - - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old| - %tr.line_holder{ id: line_code } - - if type == "match" - %td.old_line= "..." - %td.new_line= "..." - %td.line_content.matched= line - - else - %td.old_line= raw(type == "new" ? " " : line_old) - %td.new_line= raw(type == "old" ? " " : line_new) - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} " +.diff-file + .diff-header + - if diff.deleted_file + %span= diff.old_path + - else + %span= diff.new_path + - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode + %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" + %br/ + .diff-content + %table + - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old| + %tr.line_holder{ id: line_code } + - if type == "match" + %td.old_line= "..." + %td.new_line= "..." + %td.line_content.matched= line + - else + %td.old_line= raw(type == "new" ? " " : line_old) + %td.new_line= raw(type == "old" ? " " : line_new) + %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} " - - if line_code == note.line_code - = render "projects/notes/diff_notes_with_reply", notes: discussion_notes + - if line_code == note.line_code + = render "projects/notes/diff_notes_with_reply", notes: discussion_notes diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index bcaedc8bd7c..3db551e993b 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -22,7 +22,7 @@ .note-form-actions .buttons - = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button" + = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button" = yield(:note_actions) %a.btn.grouped.js-close-discussion-note-form Cancel diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index a32679a18db..8b100766e97 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -9,7 +9,7 @@ %ul %li keep stable branches secured %li forced code review before merge to protected branches - %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined_link"} + %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"} - if can? current_user, :admin_project, @project = form_for [@project, @protected_branch], html: { class: 'form-horizontal' } do |f| diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index c40f63d05b3..e60f9a44322 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -12,4 +12,4 @@ = render partial: "projects/snippets/snippet", collection: @snippets - if @snippets.empty? %li - %h3.nothing_here_message Nothing here. + .nothing-here-block Nothing here. diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 70dedcf9155..6629e47ad38 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -15,7 +15,7 @@ %span.pull-right - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'grouped btn-group-small' + = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small' - if can?(current_user, :admin_project, @project) = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do %i.icon-trash diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 2d53a5dd66a..53f3e67ff2c 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -18,7 +18,7 @@ = paginate @tags, theme: 'gitlab' - else - %h3.nothing_here_message + .nothing-here-block Repository has no tags yet. %br %small diff --git a/app/views/projects/team_members/index.html.haml b/app/views/projects/team_members/index.html.haml index acbe82919f1..6eccaafe3de 100644 --- a/app/views/projects/team_members/index.html.haml +++ b/app/views/projects/team_members/index.html.haml @@ -3,9 +3,9 @@ - if can? current_user, :admin_team_member, @project %span.pull-right - = link_to new_project_team_member_path(@project), class: "btn btn-new grouped", title: "New project member" do + = link_to new_project_team_member_path(@project), class: "btn btn-new btn-grouped", title: "New project member" do New project member - = link_to import_project_team_members_path(@project), class: "btn grouped", title: "Import members from another project" do + = link_to import_project_team_members_path(@project), class: "btn btn-grouped", title: "Import members from another project" do Import members %p.light diff --git a/app/views/projects/walls/show.html.haml b/app/views/projects/walls/show.html.haml index c6afec443f4..3e3c7c4f8dd 100644 --- a/app/views/projects/walls/show.html.haml +++ b/app/views/projects/walls/show.html.haml @@ -9,7 +9,7 @@ = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on' .note-form-actions .buttons - = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button" + = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button" .note-form-option %a.choose-btn.btn.btn-small.js-choose-note-attachment-button diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 1001bb10c4f..5dd769dcfe1 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,8 +1,8 @@ %span.pull-right - if (@wiki && @wiki.persisted?) - = link_to history_project_wiki_path(@project, @wiki), class: "btn grouped" do + = link_to history_project_wiki_path(@project, @wiki), class: "btn btn-grouped" do Page History - if can?(current_user, :write_wiki, @project) - = link_to edit_project_wiki_path(@project, @wiki), class: "btn grouped" do + = link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-grouped" do %i.icon-edit Edit diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml index 2fc93c5b742..624ec0b9b95 100644 --- a/app/views/public/projects/index.html.haml +++ b/app/views/public/projects/index.html.haml @@ -63,6 +63,6 @@ %i.icon-warning-sign Empty repository - unless @projects.present? - %h3.nothing_here_message No public projects + .nothing-here-block No public projects = paginate @projects, theme: "gitlab" diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml index 6063b4a0732..0becf531cc3 100644 --- a/app/views/shared/_filter.html.haml +++ b/app/views/shared/_filter.html.haml @@ -1,16 +1,17 @@ .side-filters.hidden-xs.hidden-sm = form_tag filter_path(entity), method: 'get' do - %fieldset.scope-filter - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:scope] == 'assigned-to-me')} - = link_to filter_path(entity, scope: 'assigned-to-me') do - Assigned to me - %li{class: ("active" if params[:scope] == 'authored')} - = link_to filter_path(entity, scope: 'authored') do - Created by me - %li{class: ("active" if params[:scope] == 'all')} - = link_to filter_path(entity, scope: 'all') do - Everyone's + - if current_user + %fieldset.scope-filter + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if params[:scope] == 'assigned-to-me')} + = link_to filter_path(entity, scope: 'assigned-to-me') do + Assigned to me + %li{class: ("active" if params[:scope] == 'authored')} + = link_to filter_path(entity, scope: 'authored') do + Created by me + %li{class: ("active" if params[:scope] == 'all')} + = link_to filter_path(entity, scope: 'all') do + Everyone's %fieldset.status-filter %legend State diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 199000656fe..087b6632e8d 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -11,5 +11,5 @@ = render 'projects/issues/issue', issue: issue = paginate @issues, theme: "gitlab" - else - %p.nothing_here_message No issues to show + .nothing-here-block No issues to show diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index ddad28339c8..f40b7be4864 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -11,4 +11,4 @@ = paginate @merge_requests, theme: "gitlab" - else - %h3.nothing_here_message No merge requests to show + .nothing-here-block No merge requests to show diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml index 1315c76fbb5..d82b08eeaa2 100644 --- a/app/views/shared/_project_filter.html.haml +++ b/app/views/shared/_project_filter.html.haml @@ -40,6 +40,12 @@ .pull-right %i.icon-remove + - if issue_label_names.empty? + .light-well + Add first label to your issues + %br + or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels + %fieldset - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any? = link_to project_entities_path, class: 'cgray pull-right' do diff --git a/app/views/snippets/_blob_content.html.haml b/app/views/snippets/_blob_content.html.haml index 81055451b66..8cec6168ab8 100644 --- a/app/views/snippets/_blob_content.html.haml +++ b/app/views/snippets/_blob_content.html.haml @@ -11,4 +11,4 @@ = render 'shared/file_hljs', blob: @snippet - else .file-content.code - %p.nothing_here_message Empty file + .nothing-here-block Empty file diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 05365dd8b88..40df42b6cf5 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -2,6 +2,6 @@ = render partial: 'snippet', collection: @snippets - if @snippets.empty? %li - %h3.nothing_here_message Nothing here. + .nothing-here-block Nothing here. = paginate @snippets, theme: 'gitlab' diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml index bf712b2c7e7..90ddc7198f6 100644 --- a/app/views/snippets/current_user_index.html.haml +++ b/app/views/snippets/current_user_index.html.haml @@ -1,9 +1,9 @@ %h3.page-title My Snippets .pull-right - = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do + = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do Add new snippet - = link_to snippets_path, class: "btn grouped" do + = link_to snippets_path, class: "btn btn-grouped" do Discover snippets %p.light diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 2f6c914a159..cea2517a8e1 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -2,9 +2,9 @@ Public snippets .pull-right - = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do + = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do Add new snippet - = link_to user_snippets_path(current_user), class: "btn grouped" do + = link_to user_snippets_path(current_user), class: "btn btn-grouped" do My snippets %p.light diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 98210af1e3d..edcaf3acf98 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -14,10 +14,10 @@ %small member since #{@user.created_at.stamp("Nov 12, 2031")} .clearfix %h4 Groups: - = render 'groups', groups: @user.groups + = render 'groups', groups: @groups %hr %h4 User Activity: = render @events .col-md-4 = render 'profile', user: @user - = render 'projects', user: @user + = render 'projects' diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml index b66b486fbc5..1784dab35b4 100644 --- a/app/views/users_groups/_users_group.html.haml +++ b/app/views/users_groups/_users_group.html.haml @@ -1,5 +1,6 @@ - user = member.user - return unless user +- show_roles = true if show_roles.nil? %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} = image_tag avatar_icon(user.email, 16), class: "avatar s16" %strong= user.name @@ -7,22 +8,23 @@ - if user == current_user %span.label.label-success It's you - %span.pull-right - %strong= member.human_access - - if show_controls - - if can?(current_user, :modify, member) - = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do - %i.icon-edit - - 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 - %i.icon-minus.icon-white - - else - = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do - %i.icon-minus.icon-white - - .edit-member.hide.js-toggle-content - = form_for [@group, member], remote: true do |f| - .alert.prepend-top-20 - = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access) - = f.submit 'Save', class: 'btn btn-save btn-small' + - if show_roles + %span.pull-right + %strong= member.human_access + - if show_controls + - if can?(current_user, :modify, member) + = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do + %i.icon-edit + - 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 + %i.icon-minus.icon-white + - else + = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + %i.icon-minus.icon-white + + .edit-member.hide.js-toggle-content + = form_for [@group, member], remote: true do |f| + .alert.prepend-top-20 + = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access) + = f.submit 'Save', class: 'btn btn-save btn-small' diff --git a/config/application.rb b/config/application.rb index c53257090cf..4d7c1415c8e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -12,7 +12,7 @@ module Gitlab # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns #{config.root}/app/models/project_services) + config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/finders #{config.root}/app/models/concerns #{config.root}/app/models/project_services) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. @@ -64,7 +64,7 @@ module Gitlab config.assets.enabled = true config.assets.paths << Emoji.images_path config.assets.precompile << "emoji/*.png" - + # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index ce57465d687..a40ce7212fe 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -217,6 +217,10 @@ production: &base ## Google analytics. Uncomment if you want it # google_analytics_id: '_your_tracking_id' + ## Piwik analytics. + # piwik_url: '_your_piwik_url' + # piwik_site_id: '_your_piwik_site_id' + ## Text under sign-in page (Markdown enabled) # sign_in_text: | #  @@ -227,6 +231,11 @@ development: test: <<: *base + gravatar: + enabled: true + gitlab: + host: localhost + port: 80 issues_tracker: redmine: title: "Redmine" diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb new file mode 100644 index 00000000000..a7ee3c59822 --- /dev/null +++ b/config/initializers/6_rack_profiler.rb @@ -0,0 +1,6 @@ +if Rails.env == 'development' + require 'rack-mini-profiler' + + # initialization is skipped so trigger it + Rack::MiniProfilerRails.initialize!(Rails.application) +end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index a02bf9d4aec..50669ece7a8 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -4,7 +4,7 @@ Devise.setup do |config| # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class with default "from" parameter. - config.mailer_sender = Gitlab.config.gitlab.email_from + config.mailer_sender = "GitLab <#{Gitlab.config.gitlab.email_from}>" # Configure the class responsible to send e-mails. diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index 50726a5a51e..5cb19171abd 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -27,8 +27,9 @@ project_urls.each_with_index do |url, i| path: group_path ) group.description = Faker::Lorem.sentence - group.owner = User.first group.save + + group.add_owner(User.first) end project_path.gsub!(".git", "") diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md new file mode 100644 index 00000000000..57b1172d5e6 --- /dev/null +++ b/doc/development/shell_commands.md @@ -0,0 +1,111 @@ +# Guidelines for shell commands in the GitLab codebase + +## Use File and FileUtils instead of shell commands + +Sometimes we invoke basic Unix commands via the shell when there is also a Ruby API for doing it. +Use the Ruby API if it exists. +http://www.ruby-doc.org/stdlib-2.0.0/libdoc/fileutils/rdoc/FileUtils.html#module-FileUtils-label-Module+Functions + +```ruby +# Wrong +system "mkdir -p tmp/special/directory" +# Better (separate tokens) +system *%W(mkdir -p tmp/special/directory) +# Best (do not use a shell command) +FileUtils.mkdir_p "tmp/special/directory" + +# Wrong +contents = `cat #{filename}` +# Correct +contents = File.read(filename) +``` + +This coding style could have prevented CVE-2013-4490. + +## Bypass the shell by splitting commands into separate tokens + +When we pass shell commands as a single string to Ruby, Ruby will let `/bin/sh` evaluate the entire string. +Essentially, we are asking the shell to evaluate a one-line script. +This creates a risk for shell injection attacks. +It is better to split the shell command into tokens ourselves. +Sometimes we use the scripting capabilities of the shell to change the working directory or set environment variables. +All of this can also be achieved securely straight from Ruby + +```ruby +# Wrong +system "cd /home/git/gitlab && bundle exec rake db:#{something} RAILS_ENV=production" +# Correct +system({'RAILS_ENV' => 'production'}, *%W(bundle exec rake db:#{something}), chdir: '/home/git/gitlab') + +# Wrong +system "touch #{myfile}" +# Better +system "touch", myfile +# Best (do not run a shell command at all) +FileUtils.touch myfile +``` + +This coding style could have prevented CVE-2013-4546. + +## Separate options from arguments with -- + +Make the difference between options and arguments clear to the argument parsers of system commands with `--`. +This is supported by many but not all Unix commands. + +To understand what `--` does, consider the problem below. + +``` +# Example +$ echo hello > -l +$ cat -l +cat: illegal option -- l +usage: cat [-benstuv] [file ...] +``` + +In the example above, the argument parser of `cat` assumes that `-l` is an option. +The solution in the example above is to make it clear to `cat` that `-l` is really an argument, not an option. +Many Unix command line tools follow the convention of separating options from arguments with `--`. + +``` +# Example (continued) +$ cat -- -l +hello +``` + +In the GitLab codebase, we avoid the option/argument ambiguity by _always_ using `--`. + +```ruby +# Wrong +system(*%W(git branch -d #{branch_name})) +# Correct +system(*%W(git branch -d -- #{branch_name})) +``` + +This coding style could have prevented CVE-2013-4582. + +## Do not use the backticks + +Capturing the output of shell commands with backticks reads nicely, but you are forced to pass the command as one string to the shell. +We explained above that this is unsafe. +In the main GitLab codebase, the solution is to use `Gitlab::Popen.popen` instead. + +```ruby +# Wrong +logs = `cd #{repo_dir} && git log` +# Correct +logs, exit_status = Gitlab::Popen.popen(%W(git log), repo_dir) + +# Wrong +user = `whoami` +# Correct +user, exit_status = Gitlab::Popen.popen(%W(whoami)) +``` + +In other repositories, such as gitlab-shell you can also use `IO.popen`. + +```ruby +# Safe IO.popen example +logs = IO.popen(%W(git log), chdir: repo_dir).read +``` + +Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error. diff --git a/features/admin/users.feature b/features/admin/users.feature index 7f503cf9235..ce9f32f50d9 100644 --- a/features/admin/users.feature +++ b/features/admin/users.feature @@ -14,3 +14,9 @@ Feature: Admin Users And Click save Then See username error message And Not changed form action url + + Scenario: Edit my user attributes + Given I visit admin users page + And click edit on my user + When I submit modified user + Then I see user attributes changed diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature index 4fa4dc26a1b..fcf8b7694f4 100644 --- a/features/project/commits/branches.feature +++ b/features/project/commits/branches.feature @@ -16,11 +16,7 @@ Feature: Project Browse branches Given I click link "Protected" Then I should see "Shop" protected branches list - # @wip - # Scenario: I can download project by branch - - # @wip - # Scenario: I can view protected branches - - # @wip - # Scenario: I can manage protected branches + Scenario: I create a branch + Given I click new branch link + When I submit new branch form + Then I should see new branch created diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature index 966905645a2..2d94b98c90b 100644 --- a/features/project/forked_merge_requests.feature +++ b/features/project/forked_merge_requests.feature @@ -32,3 +32,9 @@ Feature: Project Forked Merge Requests And I fill out an invalid "Merge Request On Forked Project" merge request And I submit the merge request Then I should see validation errors + + @javascript + Scenario: Merge request should target fork repository by default + Given I visit project "Forked Shop" merge requests page + And I click link "New Merge Request" + Then the target repository should be the original repository
\ No newline at end of file diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature index 42d6ebcb1cf..970a9e57864 100644 --- a/features/project/source/markdown_render.feature +++ b/features/project/source/markdown_render.feature @@ -4,9 +4,7 @@ Feature: Project markdown render And I own project "Delta" Given I visit project source page - # ------------------------------------------- - # README - # ------------------------------------------- + # Tree README Scenario: Tree view should have correct links in README Given I go directory which contains README file @@ -41,9 +39,7 @@ Feature: Project markdown render Then I should see rendered README which contains correct links And Header "Application details" should have correct id and link - # ------------------------------------------- - # File content - # ------------------------------------------- + # Blob Scenario: I navigate to doc directory to view documentation in master And I navigate to the doc/api/README @@ -61,9 +57,7 @@ Feature: Project markdown render And I navigate to the doc/api/README And Header "GitLab API" should have correct id and link - # ------------------------------------------- - # Markdown branch README - # ------------------------------------------- + # Markdown branch Scenario: I browse files from markdown branch When I visit markdown branch @@ -93,9 +87,31 @@ Feature: Project markdown render And I click on raketasks in doc/api/README Then I should see correct directory rendered for markdown branch - # ------------------------------------------- + Scenario: Tree markdown links view empty urls should have correct urls + When I visit markdown branch + Then The link with text "empty" should have url "tree/markdown" + When I visit markdown branch "README.md" blob + Then The link with text "empty" should have url "blob/markdown/README.md" + When I visit markdown branch "d" tree + Then The link with text "empty" should have url "tree/markdown/d" + When I visit markdown branch "d/README.md" blob + Then The link with text "empty" should have url "blob/markdown/d/README.md" + + # "ID" means "#id" on the tests below, because we are unable to escape the hash sign. + # which Spinach interprets as the start of a comment. + Scenario: All markdown links with ids should have correct urls + When I visit markdown branch + Then The link with text "ID" should have url "tree/markdownID" + Then The link with text "/ID" should have url "tree/markdownID" + Then The link with text "README.mdID" should have url "blob/markdown/README.mdID" + Then The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID" + When I visit markdown branch "README.md" blob + Then The link with text "ID" should have url "blob/markdown/README.mdID" + Then The link with text "/ID" should have url "blob/markdown/README.mdID" + Then The link with text "README.mdID" should have url "blob/markdown/README.mdID" + Then The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID" + # Wiki - # ------------------------------------------- Scenario: I create a wiki page with different links Given I go to wiki page diff --git a/features/public/public_groups.feature b/features/public/public_groups.feature new file mode 100644 index 00000000000..7f1ec718e35 --- /dev/null +++ b/features/public/public_groups.feature @@ -0,0 +1,118 @@ +Feature: Public Projects Feature + Background: + Given group "TestGroup" has private project "Enterprise" + + Scenario: I should not see group with private projects as visitor + When I visit group "TestGroup" page + Then I should be redirected to sign in page + + Scenario: I should not see group with private projects group as user + When I sign in as a user + And I visit group "TestGroup" page + Then page status code should be 404 + + Scenario: I should not see group with private and internal projects as visitor + Given group "TestGroup" has internal project "Internal" + When I visit group "TestGroup" page + Then I should be redirected to sign in page + + Scenario: I should see group with private and internal projects as user + Given group "TestGroup" has internal project "Internal" + When I sign in as a user + And I visit group "TestGroup" page + Then I should see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group issues for internal project as user + Given group "TestGroup" has internal project "Internal" + When I sign in as a user + And I visit group "TestGroup" issues page + And I change filter to Everyone's + Then I should see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group merge requests for internal project as user + Given group "TestGroup" has internal project "Internal" + When I sign in as a user + And I visit group "TestGroup" merge requests page + And I change filter to Everyone's + Then I should see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group's members as user + Given group "TestGroup" has internal project "Internal" + And "John Doe" is owner of group "TestGroup" + When I sign in as a user + And I visit group "TestGroup" members page + Then I should see group member "John Doe" + And I should not see member roles + + Scenario: I should see group with private, internal and public projects as visitor + Given group "TestGroup" has internal project "Internal" + Given group "TestGroup" has public project "Community" + When I visit group "TestGroup" page + Then I should see project "Community" items + And I should not see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group issues for public project as visitor + Given group "TestGroup" has internal project "Internal" + Given group "TestGroup" has public project "Community" + When I visit group "TestGroup" issues page + Then I should see project "Community" items + And I should not see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group merge requests for public project as visitor + Given group "TestGroup" has internal project "Internal" + Given group "TestGroup" has public project "Community" + When I visit group "TestGroup" merge requests page + Then I should see project "Community" items + And I should not see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group's members as visitor + Given group "TestGroup" has internal project "Internal" + Given group "TestGroup" has public project "Community" + And "John Doe" is owner of group "TestGroup" + When I visit group "TestGroup" members page + Then I should see group member "John Doe" + And I should not see member roles + + Scenario: I should see group with private, internal and public projects as user + Given group "TestGroup" has internal project "Internal" + Given group "TestGroup" has public project "Community" + When I sign in as a user + And I visit group "TestGroup" page + Then I should see project "Community" items + And I should see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group issues for internal and public projects as user + Given group "TestGroup" has internal project "Internal" + Given group "TestGroup" has public project "Community" + When I sign in as a user + And I visit group "TestGroup" issues page + And I change filter to Everyone's + Then I should see project "Community" items + And I should see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group merge requests for internal and public projects as user + Given group "TestGroup" has internal project "Internal" + Given group "TestGroup" has public project "Community" + When I sign in as a user + And I visit group "TestGroup" merge requests page + And I change filter to Everyone's + Then I should see project "Community" items + And I should see project "Internal" items + And I should not see project "Enterprise" items + + Scenario: I should see group's members as user + Given group "TestGroup" has internal project "Internal" + Given group "TestGroup" has public project "Community" + And "John Doe" is owner of group "TestGroup" + When I sign in as a user + And I visit group "TestGroup" members page + Then I should see group member "John Doe" + And I should not see member roles diff --git a/features/steps/admin/admin_users.rb b/features/steps/admin/admin_users.rb index 33c1344eaeb..659008dd875 100644 --- a/features/steps/admin/admin_users.rb +++ b/features/steps/admin/admin_users.rb @@ -31,4 +31,17 @@ class AdminUsers < Spinach::FeatureSteps And 'Not changed form action url' do page.should have_selector %(form[action="/admin/users/#{@user.username}"]) end + + step 'I submit modified user' do + check :user_can_create_group + click_button 'Save' + end + + step 'I see user attributes changed' do + page.should have_content 'Can create groups: Yes' + end + + step 'click edit on my user' do + find("#edit_user_#{current_user.id}").click + end end diff --git a/features/steps/project/project_browse_branches.rb b/features/steps/project/project_browse_branches.rb index ef29cc67a4e..30c8cef80c8 100644 --- a/features/steps/project/project_browse_branches.rb +++ b/features/steps/project/project_browse_branches.rb @@ -3,33 +3,49 @@ class ProjectBrowseBranches < Spinach::FeatureSteps include SharedProject include SharedPaths - Then 'I should see "Shop" recent branches list' do + step 'I should see "Shop" recent branches list' do page.should have_content "Branches" page.should have_content "master" end - Given 'I click link "All"' do + step 'I click link "All"' do click_link "All" end - Then 'I should see "Shop" all branches list' do + step 'I should see "Shop" all branches list' do page.should have_content "Branches" page.should have_content "master" end - Given 'I click link "Protected"' do + step 'I click link "Protected"' do click_link "Protected" end - Then 'I should see "Shop" protected branches list' do + step 'I should see "Shop" protected branches list' do within ".protected-branches-list" do page.should have_content "stable" page.should_not have_content "master" end end - And 'project "Shop" has protected branches' do + step 'project "Shop" has protected branches' do project = Project.find_by(name: "Shop") project.protected_branches.create(name: "stable") end + + step 'I click new branch link' do + click_link "New branch" + end + + step 'I submit new branch form' do + fill_in 'branch_name', with: 'deploy_keys' + fill_in 'ref', with: 'master' + click_button 'Create branch' + end + + step 'I should see new branch created' do + within '.all-branches' do + page.should have_content 'deploy_keys' + end + end end diff --git a/features/steps/project/project_forked_merge_requests.rb b/features/steps/project/project_forked_merge_requests.rb index 4cc99f8af55..df69cb75437 100644 --- a/features/steps/project/project_forked_merge_requests.rb +++ b/features/steps/project/project_forked_merge_requests.rb @@ -159,8 +159,11 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps step 'I fill out an invalid "Merge Request On Forked Project" merge request' do #If this isn't filled in the rest of the validations won't be triggered fill_in "merge_request_title", with: "Merge Request On Forked Project" + + select "Select branch", from: "merge_request_target_branch" + find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s - find(:select, "merge_request_target_project_id", {}).value.should == @forked_project.id.to_s + find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s find(:select, "merge_request_source_branch", {}).value.should == "" find(:select, "merge_request_target_branch", {}).value.should == "" end @@ -168,7 +171,10 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps step 'I should see validation errors' do page.should have_content "Source branch can't be blank" page.should have_content "Target branch can't be blank" - page.should have_content "Branch conflict You can not use same project/branch for source and target" + end + + step 'the target repository should be the original repository' do + page.should have_select("merge_request_target_project_id", selected: project.path_with_namespace) end def project diff --git a/features/steps/project/project_markdown_render.rb b/features/steps/project/project_markdown_render.rb index 89fbb7408c2..8fbf2753aa7 100644 --- a/features/steps/project/project_markdown_render.rb +++ b/features/steps/project/project_markdown_render.rb @@ -1,3 +1,6 @@ +# If you need to modify the existing seed repository for your tests, +# it is recommended that you make the changes on the `markdown` branch of the seed project repository, +# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info. class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps include SharedAuthentication include SharedPaths @@ -50,7 +53,7 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps end Then 'I should see correct doc/api directory rendered' do - current_path.should == project_tree_path(@project, "master/doc/api/") + current_path.should == project_tree_path(@project, "master/doc/api") page.should have_content "README.md" page.should have_content "users.md" end @@ -64,6 +67,18 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production" end + And 'I click on link "empty" in the README' do + within('.readme-holder') do + click_link "empty" + end + end + + And 'I click on link "id" in the README' do + within('.readme-holder') do + click_link "#id" + end + end + And 'I navigate to the doc/api/README' do click_link "doc" click_link "api" @@ -90,10 +105,24 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps click_link "Rake tasks" end + # Markdown branch + When 'I visit markdown branch' do visit project_tree_path(@project, "markdown") end + When 'I visit markdown branch "README.md" blob' do + visit project_blob_path(@project, "markdown/README.md") + end + + When 'I visit markdown branch "d" tree' do + visit project_tree_path(@project, "markdown/d") + end + + When 'I visit markdown branch "d/README.md" blob' do + visit project_blob_path(@project, "markdown/d/README.md") + end + Then 'I should see files from repository in markdown branch' do current_path.should == project_tree_path(@project, "markdown") page.should have_content "Gemfile" @@ -124,6 +153,50 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps page.should have_content "Get a list of users." end + # Expected link contents + + Then 'The link with text "empty" should have url "tree/markdown"' do + find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown") + end + + Then 'The link with text "empty" should have url "blob/markdown/README.md"' do + find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + end + + Then 'The link with text "empty" should have url "tree/markdown/d"' do + find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d") + end + + Then 'The link with text "empty" should have url "blob/markdown/d/README.md"' do + find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md") + end + + Then 'The link with text "ID" should have url "tree/markdownID"' do + find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id' + end + + Then 'The link with text "/ID" should have url "tree/markdownID"' do + find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id' + end + + Then 'The link with text "README.mdID" should have url "blob/markdown/README.mdID"' do + find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' + end + + Then 'The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"' do + find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id' + end + + Then 'The link with text "ID" should have url "blob/markdown/README.mdID"' do + find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' + end + + Then 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do + find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' + end + + # Wiki + Given 'I go to wiki page' do click_link "Wiki" current_path.should == project_wiki_path(@project, "home") diff --git a/features/steps/public/groups_feature.rb b/features/steps/public/groups_feature.rb new file mode 100644 index 00000000000..015deca5427 --- /dev/null +++ b/features/steps/public/groups_feature.rb @@ -0,0 +1,93 @@ +class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedGroup + include SharedProject + + step 'group "TestGroup" has private project "Enterprise"' do + group_has_project("TestGroup", "Enterprise", Gitlab::VisibilityLevel::PRIVATE) + end + + step 'group "TestGroup" has internal project "Internal"' do + group_has_project("TestGroup", "Internal", Gitlab::VisibilityLevel::INTERNAL) + end + + step 'group "TestGroup" has public project "Community"' do + group_has_project("TestGroup", "Community", Gitlab::VisibilityLevel::PUBLIC) + end + + step '"John Doe" is owner of group "TestGroup"' do + group = Group.find_by(name: "TestGroup") || create(:group, name: "TestGroup") + user = create(:user, name: "John Doe") + group.add_user(user, Gitlab::Access::OWNER) + end + + step 'I visit group "TestGroup" page' do + visit group_path(Group.find_by(name: "TestGroup")) + end + + step 'I visit group "TestGroup" issues page' do + visit issues_group_path(Group.find_by(name: "TestGroup")) + end + + step 'I visit group "TestGroup" merge requests page' do + visit merge_requests_group_path(Group.find_by(name: "TestGroup")) + end + + step 'I visit group "TestGroup" members page' do + visit members_group_path(Group.find_by(name: "TestGroup")) + end + + step 'I should not see project "Enterprise" items' do + page.should_not have_content "Enterprise" + end + + step 'I should see project "Internal" items' do + page.should have_content "Internal" + end + + step 'I should not see project "Internal" items' do + page.should_not have_content "Internal" + end + + step 'I should see project "Community" items' do + page.should have_content "Community" + end + + step 'I change filter to Everyone\'s' do + click_link "Everyone's" + end + + step 'I should see group member "John Doe"' do + page.should have_content "John Doe" + end + + step 'I should not see member roles' do + page.body.should_not match(%r{owner|developer|reporter|guest}i) + end + + protected + + def group_has_project(groupname, projectname, visibility_level) + group = Group.find_by(name: groupname) || create(:group, name: groupname) + project = create(:project, + namespace: group, + name: projectname, + path: "#{groupname}-#{projectname}", + visibility_level: visibility_level + ) + create(:issue, + title: "#{projectname} feature", + project: project + ) + create(:merge_request, + title: "#{projectname} feature implemented", + source_project: project, + target_project: project + ) + create(:closed_issue_event, + project: project + ) + end +end + diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 9c39a226e1b..f80d8d06475 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -2,7 +2,7 @@ module SharedDiffNote include Spinach::DSL Given 'I cancel the diff comment' do - within(".file") do + within(".diff-file") do find(".js-close-discussion-note-form").click end end @@ -13,14 +13,14 @@ module SharedDiffNote end Given 'I haven\'t written any diff comment text' do - within(".file") do + within(".diff-file") do fill_in "note[note]", with: "" end end Given 'I leave a diff comment like "Typo, please fix"' do find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click - within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do + within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do fill_in "note[note]", with: "Typo, please fix" find(".js-comment-button").trigger("click") sleep 0.05 @@ -29,7 +29,7 @@ module SharedDiffNote Given 'I preview a diff comment text like "Should fix it :smile:"' do find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click - within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do + within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do fill_in "note[note]", with: "Should fix it :smile:" find(".js-note-preview-button").trigger("click") end @@ -38,7 +38,7 @@ module SharedDiffNote Given 'I preview another diff comment text like "DRY this up"' do find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_57_41"]').click - within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do + within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do fill_in "note[note]", with: "DRY this up" find(".js-note-preview-button").trigger("click") end @@ -53,13 +53,13 @@ module SharedDiffNote end Given 'I write a diff comment like ":-1: I don\'t like this"' do - within(".file") do + within(".diff-file") do fill_in "note[note]", with: ":-1: I don\'t like this" end end Given 'I submit the diff comment' do - within(".file") do + within(".diff-file") do click_button("Add Comment") end end @@ -67,49 +67,49 @@ module SharedDiffNote Then 'I should not see the diff comment form' do - within(".file") do + within(".diff-file") do page.should_not have_css("form.new_note") end end Then 'I should not see the diff comment preview button' do - within(".file") do + within(".diff-file") do page.should have_css(".js-note-preview-button", visible: false) end end Then 'I should not see the diff comment text field' do - within(".file") do + within(".diff-file") do page.should have_css(".js-note-text", visible: false) end end Then 'I should only see one diff form' do - within(".file") do + within(".diff-file") do page.should have_css("form.new_note", count: 1) end end Then 'I should see a diff comment form with ":-1: I don\'t like this"' do - within(".file") do + within(".diff-file") do page.should have_field("note[note]", with: ":-1: I don\'t like this") end end Then 'I should see a diff comment saying "Typo, please fix"' do - within(".file .note") do + within(".diff-file .note") do page.should have_content("Typo, please fix") end end Then 'I should see a discussion reply button' do - within(".file") do + within(".diff-file") do page.should have_link("Reply") end end Then 'I should see a temporary diff comment form' do - within(".file") do + within(".diff-file") do page.should have_css(".js-temp-notes-holder form.new_note") end end @@ -119,37 +119,37 @@ module SharedDiffNote end Then 'I should see an empty diff comment form' do - within(".file") do + within(".diff-file") do page.should have_field("note[note]", with: "") end end Then 'I should see the cancel comment button' do - within(".file form") do + within(".diff-file form") do page.should have_css(".js-close-discussion-note-form", text: "Cancel") end end Then 'I should see the diff comment preview' do - within(".file form") do + within(".diff-file form") do page.should have_css(".js-note-preview", visible: false) end end Then 'I should see the diff comment edit button' do - within(".file") do + within(".diff-file") do page.should have_css(".js-note-edit-button", visible: true) end end Then 'I should see the diff comment preview button' do - within(".file") do + within(".diff-file") do page.should have_css(".js-note-preview-button", visible: true) end end Then 'I should see two separate previews' do - within(".file") do + within(".diff-file") do page.should have_css(".js-note-preview", visible: true, count: 2) page.should have_content("Should fix it") page.should have_content("DRY this up") diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index 5283cf0b821..e2fbafb3899 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -1,8 +1,16 @@ require 'fileutils' +require 'open3' module Gitlab module Popen - def popen(cmd, path) + extend self + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) + raise "System commands must be given as an array of strings" + end + + path ||= Dir.pwd vars = { "PWD" => path } options = { chdir: path } @@ -12,10 +20,10 @@ module Gitlab @cmd_output = "" @cmd_status = 0 - Open3.popen3(vars, cmd, options) do |stdin, stdout, stderr, wait_thr| - @cmd_status = wait_thr.value.exitstatus + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| @cmd_output << stdout.read @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus end return @cmd_output, @cmd_status diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index 353c3024aad..bcf3012bd92 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -33,7 +33,7 @@ module Gitlab end def create - output, status = popen("git clone #{project.repository.path_to_repo} #{path}", + output, status = popen(%W(git clone -- #{project.repository.path_to_repo} #{path}), Gitlab.config.satellites.path) log("PID: #{project.id}: git clone #{project.repository.path_to_repo} #{path}") diff --git a/spec/factories.rb b/spec/factories.rb index 37436e53b95..7fc2b7c5e97 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -34,6 +34,16 @@ FactoryGirl.define do creator end + # Generates a test repository from the repository stored under `spec/seed_project.tar.gz`. + # Once you run `rake gitlab:setup`, you can see what the repository looks like under `tmp/repositories/gitlabhq`. + # In order to modify files in the repository, you must untar the seed, modify and remake the tar. + # Before recompressing, do not forget to `git checkout master`. + # After recompressing, you need to run `RAILS_ENV=test bundle exec rake gitlab:setup` to regenerate the seeds under tmp. + # + # If you want to modify the repository only for an specific type of tests, e.g., markdown tests, + # consider using a feature branch to reduce the chances of collision with other tests. + # Create a new commit, and use the same commit message that you will use for the change in the main repo. + # Changing the commig message and SHA of branch `master` may break tests. factory :project, parent: :empty_project do path { 'gitlabhq' } diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index da723ae39bd..a3d8c462bf6 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -166,7 +166,7 @@ describe "On a merge request diff", js: true, focus: true do end it "should be removed when canceled" do - within(".file form[rel$='4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185']") do + within(".diff-file form[rel$='4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185']") do find(".js-close-discussion-note-form").trigger("click") end diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group/group_access_spec.rb index dea957962a8..c262d76ab54 100644 --- a/spec/features/security/group_access_spec.rb +++ b/spec/features/security/group/group_access_spec.rb @@ -14,6 +14,7 @@ describe "Group access" do let(:master) { create(:user) } let(:reporter) { create(:user) } let(:guest) { create(:user) } + let(:nonmember) { create(:user) } before do group.add_user(owner, Gitlab::Access::OWNER) diff --git a/spec/features/security/group/internal_group_access_spec.rb b/spec/features/security/group/internal_group_access_spec.rb new file mode 100644 index 00000000000..0c354f02456 --- /dev/null +++ b/spec/features/security/group/internal_group_access_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe "Group with internal project access" do + describe "Group" do + let(:group) { create(:group) } + + let(:owner) { create(:owner) } + let(:master) { create(:user) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + let(:nonmember) { create(:user) } + + before do + group.add_user(owner, Gitlab::Access::OWNER) + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + group.add_user(guest, Gitlab::Access::GUEST) + + create(:project, group: group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) + end + + describe "GET /groups/:path" do + subject { group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /groups/:path/issues" do + subject { issues_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /groups/:path/merge_requests" do + subject { merge_requests_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /groups/:path/members" do + subject { members_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /groups/:path/edit" do + subject { edit_group_path(group) } + + it { should be_allowed_for owner } + it { should be_denied_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + end +end diff --git a/spec/features/security/group/mixed_group_access_spec.rb b/spec/features/security/group/mixed_group_access_spec.rb new file mode 100644 index 00000000000..82e816e388a --- /dev/null +++ b/spec/features/security/group/mixed_group_access_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe "Group access" do + describe "Group" do + let(:group) { create(:group) } + + let(:owner) { create(:owner) } + let(:master) { create(:user) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + let(:nonmember) { create(:user) } + + before do + group.add_user(owner, Gitlab::Access::OWNER) + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + group.add_user(guest, Gitlab::Access::GUEST) + + create(:project, path: "internal_project", group: group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) + create(:project, path: "public_project", group: group, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + describe "GET /groups/:path" do + subject { group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /groups/:path/issues" do + subject { issues_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /groups/:path/merge_requests" do + subject { merge_requests_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /groups/:path/members" do + subject { members_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /groups/:path/edit" do + subject { edit_group_path(group) } + + it { should be_allowed_for owner } + it { should be_denied_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + end +end diff --git a/spec/features/security/group/public_group_access_spec.rb b/spec/features/security/group/public_group_access_spec.rb new file mode 100644 index 00000000000..a9c0afbb60f --- /dev/null +++ b/spec/features/security/group/public_group_access_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe "Group with public project access" do + describe "Group" do + let(:group) { create(:group) } + + let(:owner) { create(:owner) } + let(:master) { create(:user) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + let(:nonmember) { create(:user) } + + before do + group.add_user(owner, Gitlab::Access::OWNER) + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + group.add_user(guest, Gitlab::Access::GUEST) + + create(:project, group: group, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + describe "GET /groups/:path" do + subject { group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /groups/:path/issues" do + subject { issues_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /groups/:path/merge_requests" do + subject { merge_requests_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /groups/:path/members" do + subject { members_group_path(group) } + + it { should be_allowed_for owner } + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_allowed_for :visitor } + end + + describe "GET /groups/:path/edit" do + subject { edit_group_path(group) } + + it { should be_allowed_for owner } + it { should be_denied_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + end +end diff --git a/spec/services/filtering_service_spec.rb b/spec/finders/issues_finder_spec.rb index 92971d0be12..7489e56f423 100644 --- a/spec/services/filtering_service_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -1,13 +1,10 @@ require 'spec_helper' -describe FilteringService do +describe IssuesFinder do let(:user) { create :user } let(:user2) { create :user } let(:project1) { create(:project) } let(:project2) { create(:project) } - let(:merge_request1) { create(:merge_request, author: user, source_project: project1, target_project: project2) } - let(:merge_request2) { create(:merge_request, author: user, source_project: project2, target_project: project1) } - let(:merge_request3) { create(:merge_request, author: user, source_project: project2, target_project: project2) } let(:issue1) { create(:issue, assignee: user, project: project1) } let(:issue2) { create(:issue, assignee: user, project: project2) } let(:issue3) { create(:issue, assignee: user2, project: project2) } @@ -18,27 +15,7 @@ describe FilteringService do project2.team << [user2, :developer] end - describe 'merge requests' do - before :each do - merge_request1 - merge_request2 - merge_request3 - end - - it 'should filter by scope' do - params = { scope: 'authored', state: 'opened' } - merge_requests = FilteringService.new.execute(MergeRequest, user, params) - merge_requests.size.should == 3 - end - - it 'should filter by project' do - params = { project_id: project1.id, scope: 'authored', state: 'opened' } - merge_requests = FilteringService.new.execute(MergeRequest, user, params) - merge_requests.size.should == 1 - end - end - - describe 'issues' do + describe :execute do before :each do issue1 issue2 @@ -47,31 +24,31 @@ describe FilteringService do it 'should filter by all' do params = { scope: "all", state: 'opened' } - issues = FilteringService.new.execute(Issue, user, params) + issues = IssuesFinder.new.execute(user, params) issues.size.should == 3 end it 'should filter by assignee' do params = { scope: "assigned-to-me", state: 'opened' } - issues = FilteringService.new.execute(Issue, user, params) + issues = IssuesFinder.new.execute(user, params) issues.size.should == 2 end it 'should filter by project' do params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id } - issues = FilteringService.new.execute(Issue, user, params) + issues = IssuesFinder.new.execute(user, params) issues.size.should == 1 end it 'should be empty for unauthorized user' do params = { scope: "all", state: 'opened' } - issues = FilteringService.new.execute(Issue, nil, params) + issues = IssuesFinder.new.execute(nil, params) issues.size.should be_zero end it 'should not include unauthorized issues' do params = { scope: "all", state: 'opened' } - issues = FilteringService.new.execute(Issue, user2, params) + issues = IssuesFinder.new.execute(user2, params) issues.size.should == 2 issues.should_not include(issue1) issues.should include(issue2) diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb new file mode 100644 index 00000000000..76f9e753dd2 --- /dev/null +++ b/spec/finders/merge_requests_finder_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe MergeRequestsFinder do + let(:user) { create :user } + let(:user2) { create :user } + let(:project1) { create(:project) } + let(:project2) { create(:project) } + let(:merge_request1) { create(:merge_request, author: user, source_project: project1, target_project: project2) } + let(:merge_request2) { create(:merge_request, author: user, source_project: project2, target_project: project1) } + let(:merge_request3) { create(:merge_request, author: user, source_project: project2, target_project: project2) } + + before do + project1.team << [user, :master] + project2.team << [user, :developer] + project2.team << [user2, :developer] + end + + describe :execute do + before :each do + merge_request1 + merge_request2 + merge_request3 + end + + it 'should filter by scope' do + params = { scope: 'authored', state: 'opened' } + merge_requests = MergeRequestsFinder.new.execute(user, params) + merge_requests.size.should == 3 + end + + it 'should filter by project' do + params = { project_id: project1.id, scope: 'authored', state: 'opened' } + merge_requests = MergeRequestsFinder.new.execute(user, params) + merge_requests.size.should == 1 + end + end +end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb new file mode 100644 index 00000000000..cc6ee82ab75 --- /dev/null +++ b/spec/finders/projects_finder_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe ProjectsFinder do + let(:user) { create :user } + let(:group) { create :group } + + let(:project1) { create(:empty_project, group: group, visibility_level: Project::PUBLIC) } + let(:project2) { create(:empty_project, group: group, visibility_level: Project::INTERNAL) } + let(:project3) { create(:empty_project, group: group, visibility_level: Project::PRIVATE) } + let(:project4) { create(:empty_project, group: group, visibility_level: Project::PRIVATE) } + + context 'non authenticated' do + subject { ProjectsFinder.new.execute(nil, group: group) } + + it { should include(project1) } + it { should_not include(project2) } + it { should_not include(project3) } + it { should_not include(project4) } + end + + context 'authenticated' do + subject { ProjectsFinder.new.execute(user, group: group) } + + it { should include(project1) } + it { should include(project2) } + it { should_not include(project3) } + it { should_not include(project4) } + end + + context 'authenticated, project member' do + before { project3.team << [user, :developer] } + + subject { ProjectsFinder.new.execute(user, group: group) } + + it { should include(project1) } + it { should include(project2) } + it { should include(project3) } + it { should_not include(project4) } + end + + context 'authenticated, group member' do + before { group.add_user(user, Gitlab::Access::DEVELOPER) } + + subject { ProjectsFinder.new.execute(user, group: group) } + + it { should include(project1) } + it { should include(project2) } + it { should include(project3) } + it { should include(project4) } + end +end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index a445c18f009..7e98b7944f1 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -454,7 +454,7 @@ describe GitlabMarkdownHelper do it "should handle relative urls in reference links for a directory in master" do actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n" - expected = "<p><a href=\"/#{project.path_with_namespace}/tree/master/doc/api/\">GitLab API doc directory</a></p>\n" + expected = "<p><a href=\"/#{project.path_with_namespace}/tree/master/doc/api\">GitLab API doc directory</a></p>\n" markdown(actual).should match(expected) end diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 4791be41613..76d506eb3c0 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -10,7 +10,7 @@ describe 'Gitlab::Popen', no_db: true do context 'zero status' do before do - @output, @status = @klass.new.popen('ls', path) + @output, @status = @klass.new.popen(%W(ls), path) end it { @status.should be_zero } @@ -19,11 +19,27 @@ describe 'Gitlab::Popen', no_db: true do context 'non-zero status' do before do - @output, @status = @klass.new.popen('cat NOTHING', path) + @output, @status = @klass.new.popen(%W(cat NOTHING), path) end it { @status.should == 1 } it { @output.should include('No such file or directory') } end + + context 'unsafe string command' do + it 'raises an error when it gets called with a string argument' do + expect { @klass.new.popen('ls', path) }.to raise_error + end + end + + context 'without a directory argument' do + before do + @output, @status = @klass.new.popen(%W(ls)) + end + + it { @status.should be_zero } + it { @output.should include('spec') } + end + end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 88cae0bb756..6ba4d97ad4a 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -4,6 +4,7 @@ describe Notify do include EmailSpec::Helpers include EmailSpec::Matchers + let(:gitlab_sender) { Gitlab.config.gitlab.email_from } let(:recipient) { create(:user, email: 'recipient@example.com') } let(:project) { create(:project) } @@ -13,18 +14,28 @@ describe Notify do end end + shared_examples 'an email sent from GitLab' do + it 'is sent from GitLab' do + sender = subject.header[:from].addrs[0] + sender.display_name.should eq('GitLab') + sender.address.should eq(gitlab_sender) + end + end + describe 'for new users, the email' do let(:example_site_path) { root_path } let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) } subject { Notify.new_user_email(new_user.id, new_user.password) } + it_behaves_like 'an email sent from GitLab' + it 'is sent to the new user' do should deliver_to new_user.email end it 'has the correct subject' do - should have_subject /^gitlab \| Account was created for you$/i + should have_subject /^Account was created for you$/i end it 'contains the new user\'s login name' do @@ -47,12 +58,14 @@ describe Notify do subject { Notify.new_user_email(new_user.id, new_user.password) } + it_behaves_like 'an email sent from GitLab' + it 'is sent to the new user' do should deliver_to new_user.email end it 'has the correct subject' do - should have_subject /^gitlab \| Account was created for you$/i + should have_subject /^Account was created for you$/i end it 'contains the new user\'s login name' do @@ -73,12 +86,14 @@ describe Notify do subject { Notify.new_ssh_key_email(key.id) } + it_behaves_like 'an email sent from GitLab' + it 'is sent to the new user' do should deliver_to key.user.email end it 'has the correct subject' do - should have_subject /^gitlab \| SSH key was added to your account$/i + should have_subject /^SSH key was added to your account$/i end it 'contains the new ssh key title' do @@ -100,7 +115,7 @@ describe Notify do end it 'has the correct subject' do - should have_subject /^gitlab \| Email was added to your account$/i + should have_subject /^Email was added to your account$/i end it 'contains the new email address' do @@ -114,17 +129,24 @@ describe Notify do context 'for a project' do describe 'items that are assignable, the email' do + let(:current_user) { create(:user, email: "current@email.com") } let(:assignee) { create(:user, email: 'assignee@example.com') } let(:previous_assignee) { create(:user, name: 'Previous Assignee') } shared_examples 'an assignee email' do + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + sender.display_name.should eq(current_user.name) + sender.address.should eq(gitlab_sender) + end + it 'is sent to the assignee' do should deliver_to assignee.email end end context 'for issues' do - let(:issue) { create(:issue, assignee: assignee, project: project ) } + let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project ) } describe 'that are new' do subject { Notify.new_issue_email(issue.assignee_id, issue.id) } @@ -132,7 +154,7 @@ describe Notify do it_behaves_like 'an assignee email' it 'has the correct subject' do - should have_subject /#{project.name} \| New issue ##{issue.iid} \| #{issue.title}/ + should have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/ end it 'contains a link to the new issue' do @@ -141,14 +163,18 @@ describe Notify do end describe 'that have been reassigned' do - before(:each) { issue.stub(:assignee_id_was).and_return(previous_assignee.id) } - - subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id) } + subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) } it_behaves_like 'a multiple recipients email' + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + sender.display_name.should eq(current_user.name) + sender.address.should eq(gitlab_sender) + end + it 'has the correct subject' do - should have_subject /Changed issue ##{issue.iid} \| #{issue.title}/ + should have_subject /#{issue.title} \(##{issue.iid}\)/ end it 'contains the name of the previous assignee' do @@ -165,12 +191,17 @@ describe Notify do end describe 'status changed' do - let(:current_user) { create(:user, email: "current@email.com") } let(:status) { 'closed' } subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + sender.display_name.should eq(current_user.name) + sender.address.should eq(gitlab_sender) + end + it 'has the correct subject' do - should have_subject /Changed issue ##{issue.iid} \| #{issue.title}/i + should have_subject /#{issue.title} \(##{issue.iid}\)/i end it 'contains the new status' do @@ -189,7 +220,7 @@ describe Notify do end context 'for merge requests' do - let(:merge_request) { create(:merge_request, assignee: assignee, source_project: project, target_project: project) } + let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) } describe 'that are new' do subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) } @@ -197,7 +228,7 @@ describe Notify do it_behaves_like 'an assignee email' it 'has the correct subject' do - should have_subject /New merge request ##{merge_request.iid}/ + should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/ end it 'contains a link to the new merge request' do @@ -214,14 +245,18 @@ describe Notify do end describe 'that are reassigned' do - before(:each) { merge_request.stub(:assignee_id_was).and_return(previous_assignee.id) } - - subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id) } + subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) } it_behaves_like 'a multiple recipients email' + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + sender.display_name.should eq(current_user.name) + sender.address.should eq(gitlab_sender) + end + it 'has the correct subject' do - should have_subject /Changed merge request ##{merge_request.iid}/ + should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/ end it 'contains the name of the previous assignee' do @@ -245,6 +280,8 @@ describe Notify do let(:user) { create(:user) } subject { Notify.project_was_moved_email(project.id, user.id) } + it_behaves_like 'an email sent from GitLab' + it 'has the correct subject' do should have_subject /Project was moved/ end @@ -265,6 +302,9 @@ describe Notify do project: project, user: user) } subject { Notify.project_access_granted_email(users_project.id) } + + it_behaves_like 'an email sent from GitLab' + it 'has the correct subject' do should have_subject /Access to project was granted/ end @@ -285,6 +325,12 @@ describe Notify do end shared_examples 'a note email' do + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + sender.display_name.should eq(note_author.name) + sender.address.should eq(gitlab_sender) + end + it 'is sent to the given recipient' do should deliver_to recipient.email end @@ -324,7 +370,7 @@ describe Notify do it_behaves_like 'a note email' it 'has the correct subject' do - should have_subject /Note for commit #{commit.short_id}/ + should have_subject /#{commit.title} \(#{commit.short_id}\)/ end it 'contains a link to the commit' do @@ -342,7 +388,7 @@ describe Notify do it_behaves_like 'a note email' it 'has the correct subject' do - should have_subject /Note for merge request ##{merge_request.iid}/ + should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/ end it 'contains a link to the merge request note' do @@ -360,7 +406,7 @@ describe Notify do it_behaves_like 'a note email' it 'has the correct subject' do - should have_subject /Note for issue ##{issue.iid}/ + should have_subject /#{issue.title} \(##{issue.iid}\)/ end it 'contains a link to the issue note' do @@ -377,6 +423,8 @@ describe Notify do subject { Notify.group_access_granted_email(membership.id) } + it_behaves_like 'an email sent from GitLab' + it 'has the correct subject' do should have_subject /Access to group was granted/ end @@ -401,6 +449,8 @@ describe Notify do subject { ActionMailer::Base.deliveries.last } + it_behaves_like 'an email sent from GitLab' + it 'is sent to the new user' do should deliver_to 'new-email@mail.com' end @@ -421,6 +471,12 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + sender.display_name.should eq(user.name) + sender.address.should eq(gitlab_sender) + end + it 'is sent to recipient' do should deliver_to 'devs@company.name' end diff --git a/spec/seed_project.tar.gz b/spec/seed_project.tar.gz Binary files differindex ee8c3f23c70..92b9587e3f7 100644 --- a/spec/seed_project.tar.gz +++ b/spec/seed_project.tar.gz diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index e378be04255..077ad8b6e12 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -137,11 +137,11 @@ describe NotificationService do end def should_email(user_id) - Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id) + Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id) end def should_not_email(user_id) - Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id) + Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id) end end @@ -201,11 +201,11 @@ describe NotificationService do end def should_email(user_id) - Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id) + Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id) end def should_not_email(user_id) - Notify.should_not_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id) + Notify.should_not_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id) end end |
