diff options
220 files changed, 3265 insertions, 1907 deletions
diff --git a/.gitignore b/.gitignore index d22760e7780..94a210b9461 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ db/data.yml .idea .DS_Store .chef +vendor/bundle/* diff --git a/CHANGELOG b/CHANGELOG index 2eca1f14c4f..2c6152e77dd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,20 @@ +v 4.0.0 + - [API] list, create issue notes + - [API] list, create snippet notes + - [API] list, create wall notes + - Remove project code - use path instead + - added username field to user + - rake task to fill usernames based on emails create namespaces for users + - STI Group < Namespace + - Project has namespace_id + - Projects with namespaces also namespaced in gitolite and stored in subdir + - Moving project to group will move it under group namespace + - Ability to move project from namespaces to another + - Fixes commit patches getting escaped (see #2036) + - Support diff and patch generation for commits and merge request + - MergeReqest doesn't generate a temporary file for the patch any more + - Update the UI to allow downloading Patch or Diff + v 3.1.0 - Updated gems - Services: Gitlab CI integration @@ -34,7 +51,7 @@ v 3.0.0 - Fixed bug with gitolite keys - UI improved - Increased perfomance of application - - Show user avatar in last commit when browsing Files + - Show user avatar in last commit when browsing Files - Refactored Gitlab::Merge - Use Font Awsome for icons - Separate observing of Note and MergeRequestsa @@ -11,7 +11,6 @@ end gem "rails", "3.2.9" # Supported DBs -gem "sqlite3", group: :sqlite gem "mysql2", group: :mysql gem "pg", group: :postgres @@ -27,7 +26,7 @@ gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: gem "omniauth-ldap", git: "https://github.com/gitlabhq/omniauth-ldap.git", ref: 'f038dd852d7bd473a557e385d5d7c2fd5dc1dc2e' gem 'yaml_db', git: "https://github.com/gitlabhq/yaml_db.git", ref: '98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd' gem 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8' -gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '212fd40bea61f3c6a167223768e7295dc32bbc10' +gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e' # Gitolite client (for work with gitolite-admin repo) gem "gitolite", '1.1.0' @@ -105,7 +104,7 @@ group :assets do gem "jquery-rails", "2.1.3" gem "jquery-ui-rails", "2.0.2" gem "modernizr", "2.6.2" - gem "raphael-rails", "2.1.0" + gem "raphael-rails", "1.5.2" gem 'bootstrap-sass', "2.2.1.1" gem "font-awesome-sass-rails", "~> 2.0.0" gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' @@ -139,7 +138,7 @@ group :development, :test do gem 'rb-inotify', require: linux_only('rb-inotify') # PhantomJS driver for Capybara - gem 'poltergeist' + gem 'poltergeist', git: 'https://github.com/jonleighton/poltergeist.git', ref: '5c2e092001074a8cf09f332d3714e9ba150bc8ca' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 0e3a9810dbe..0c9563e4ab2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,10 +26,10 @@ GIT GIT remote: https://github.com/gitlabhq/grit_ext.git - revision: 212fd40bea61f3c6a167223768e7295dc32bbc10 - ref: 212fd40bea61f3c6a167223768e7295dc32bbc10 + revision: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e + ref: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e specs: - grit_ext (0.6.0) + grit_ext (0.6.1) charlock_holmes (~> 0.6.9) GIT @@ -59,6 +59,18 @@ GIT specs: yaml_db (0.2.2) +GIT + remote: https://github.com/jonleighton/poltergeist.git + revision: 5c2e092001074a8cf09f332d3714e9ba150bc8ca + ref: 5c2e092001074a8cf09f332d3714e9ba150bc8ca + specs: + poltergeist (1.0.2) + capybara (~> 1.1) + childprocess (~> 0.3) + faye-websocket (~> 0.4, >= 0.4.4) + http_parser.rb (~> 0.5.3) + multi_json (~> 1.0) + GEM remote: http://rubygems.org/ specs: @@ -279,12 +291,6 @@ GEM omniauth-oauth (~> 1.0) orm_adapter (0.4.0) pg (0.14.1) - poltergeist (1.0.2) - capybara (~> 1.1) - childprocess (~> 0.3) - faye-websocket (~> 0.4, >= 0.4.4) - http_parser.rb (~> 0.5.3) - multi_json (~> 1.0) polyglot (0.3.3) posix-spawn (0.3.6) pry (0.9.10) @@ -329,7 +335,7 @@ GEM thor (>= 0.14.6, < 2.0) raindrops (0.10.0) rake (10.0.1) - raphael-rails (2.1.0) + raphael-rails (1.5.2) rb-fsevent (0.9.2) rb-inotify (0.8.8) ffi (>= 0.5.0) @@ -404,7 +410,6 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sqlite3 (1.3.6) stamp (0.3.0) test_after_commit (0.0.1) therubyracer (0.10.2) @@ -490,14 +495,14 @@ DEPENDENCIES omniauth-ldap! omniauth-twitter pg - poltergeist + poltergeist! pry pygments.rb! quiet_assets (~> 1.0.1) rack-mini-profiler rails (= 3.2.9) rails-dev-tweaks - raphael-rails (= 2.1.0) + raphael-rails (= 1.5.2) rb-fsevent rb-inotify redcarpet (~> 2.2.2) @@ -512,7 +517,6 @@ DEPENDENCIES simplecov six spinach-rails - sqlite3 stamp test_after_commit therubyracer @@ -1 +1 @@ -3.1.0 +4.0.0pre diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index 76454c29269..1dafdf4bd8b 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -10,3 +10,8 @@ $ -> $('.log-tabs a').click (e) -> e.preventDefault() $(this).tab('show') + + $('.log-bottom').click (e) -> + e.preventDefault() + visible_log = $(".file_content:visible") + visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast") diff --git a/app/assets/javascripts/merge_requests.js b/app/assets/javascripts/merge_requests.js index cc6b0771af5..170a0479de7 100644 --- a/app/assets/javascripts/merge_requests.js +++ b/app/assets/javascripts/merge_requests.js @@ -14,14 +14,6 @@ var MergeRequest = { $(".mr_show_all_commits").bind("click", function() { self.showAllCommits(); }); - - $(".line_note_link, .line_note_reply_link").live("click", function(e) { - var form = $(".per_line_form"); - $(this).parent().parent().after(form); - form.find("#note_line_code").val($(this).attr("line_code")); - form.show(); - return false; - }); }, initMergeWidget: diff --git a/app/assets/javascripts/projects.js.coffee b/app/assets/javascripts/projects.js.coffee index 3059723dfa0..1808f0578f6 100644 --- a/app/assets/javascripts/projects.js.coffee +++ b/app/assets/javascripts/projects.js.coffee @@ -1,8 +1,4 @@ window.Projects = -> - $('#project_name').on 'change', -> - slug = slugify $(@).val() - $('#project_code, #project_path').val slug - $('.new_project, .edit_project').on 'ajax:before', -> $('.project_new_holder, .project_edit_holder').hide() $('.save-project-loader').show() diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee index 3f8ed6c2552..5003f9b00c7 100644 --- a/app/assets/javascripts/tree.js.coffee +++ b/app/assets/javascripts/tree.js.coffee @@ -28,7 +28,7 @@ $ -> return false $('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live 'click', (e) -> - History.pushState(null, null, $(@).attr('href')) + History.pushState(null, null, decodeURIComponent($(@).attr('href'))) return false History.Adapter.bind window, 'statechange', -> diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index e45cb876e7d..c82ddc185b3 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -1,23 +1,23 @@ /** LAYOUT **/ body { - margin-bottom:20px; + margin-bottom: 20px; } .container { - padding-top:0; - z-index:5; + padding-top: 0; + z-index: 5; } .container .content { - margin:0 0; + margin: 0 0; } .container .sidebar { width: 200px; - height:100%; - min-height:450px; - float:right; + height: 100%; + min-height: 450px; + float: right; } @@ -29,25 +29,25 @@ body { .help li { color:#111 } .back_link { - text-decoration:underline; - font-size:14px; - font-weight:bold; - padding:10px 0; - padding-bottom:0; + text-decoration: underline; + font-size: 14px; + font-weight: bold; + padding: 10px 0; + padding-bottom: 0; } .info_link { - margin-right:5px; - float:left; + margin-right: 5px; + float: left; img { - width:20px; + width: 20px; } } .download_repo_link { background: url("images.png") no-repeat 0 -48px; - padding-left:20px; + padding-left: 20px; } table a code { @@ -61,32 +61,32 @@ table a code { } .loading { - margin:20px auto; + margin: 20px auto; background: url(ajax_loader.gif) no-repeat center center; - width:40px; - height:40px; + width: 40px; + height: 40px; } /** FLASH message **/ #flash_container { - height:50px; - position:fixed; - z-index:10001; - top:0px; - width:100%; - margin-bottom:15px; - overflow:hidden; - background:white; - cursor:pointer; - border-bottom:1px solid #ccc; + height: 50px; + position: fixed; + z-index: 10001; + top: 0px; + width: 100%; + margin-bottom: 15px; + overflow: hidden; + background: white; + cursor: pointer; + border-bottom: 1px solid #ccc; h4 { - color:#666; - font-size:18px; - line-height:38px; - padding-top:5px; - margin:2px; - font-weight:normal; + color: #666; + font-size: 18px; + line-height: 38px; + padding-top: 5px; + margin: 2px; + font-weight: normal; } } @@ -95,39 +95,39 @@ table a code { } .handle:hover { - cursor:move; + cursor: move; } span.update-author { - display:block; + display: block; } span.update-author { - color:#999; - font-weight:normal; - font-style:italic; + color: #999; + font-weight: normal; + font-style: italic; } span.update-author strong { - font-weight:bold; + font-weight: bold; font-style: normal; } /** UPDATE ITEM **/ span.update-author { - display:block; + display: block; } /** END UPDATE ITEM **/ .dashboard-loader { - float:left; - margin:10px; - display:none; + float: left; + margin: 10px; + display: none; } .user-mention { - color:#2FA0BB; - font-weight:bold; + color: #2FA0BB; + font-weight: bold; } .neib { - margin-right:10px; + margin-right: 10px; } .label { @@ -136,9 +136,9 @@ span.update-author { &.label-tag { background: none; border: none; - padding:4px 6px; - color:#444; - text-shadow:0 0 1px #fff; + padding: 4px 6px; + color: #444; + text-shadow: 0 0 1px #fff; &.grouped { float: left; @@ -149,9 +149,9 @@ span.update-author { &.label-issue { background-color: #eee; border: 1px solid #ccc; - padding:4px 6px; - color:#444; - text-shadow:0 0 1px #fff; + padding: 4px 6px; + color: #444; + text-shadow: 0 0 1px #fff; &.grouped { float: left; @@ -201,21 +201,21 @@ form { .field_with_errors { - display:inline; + display: inline; } ul.breadcrumb { - background:white; - border:none; + background: white; + border: none; li { display: inline; text-shadow: 0 1px 0 white } a { - color:#474D57; - font-weight:bold; - font-size:14px; + color: #474D57; + font-weight: bold; + font-size: 14px; } .arrow { @@ -225,31 +225,31 @@ ul.breadcrumb { float: left; position: relative; left: -10px; - padding:0; - margin:0; + padding: 0; + margin: 0; } } input[type=text] { &.large_text { - padding:6px; - font-size:16px; + padding: 6px; + font-size: 16px; } } input.git_clone_url { - width:325px; + width: 325px; } .merge-request-form-holder { select { - width:300px; + width: 300px; } } /** Issues **/ #issue_assignee_id { - width:300px; + width: 300px; } #new_issue_dialog textarea{ @@ -257,21 +257,21 @@ input.git_clone_url { } .project_list_url { - width:250px; + width: 250px; background:#fff !important; } /** bordered list **/ ul.bordered-list { - margin:5px 0px; - padding:0px; + margin: 5px 0px; + padding: 0px; li { padding: 5px 0; border-bottom: 1px solid #EEE; overflow: hidden; display: block; - margin:0px; + margin: 0px; } } @@ -287,10 +287,10 @@ ul.bordered-list li:last-child { border:none } li.commit { .avatar { - width:24px; + width: 24px; top:-5px; - margin-right:10px; - margin-left:10px; + margin-right: 10px; + margin-left: 10px; } code { @@ -310,7 +310,7 @@ p.time { .styled_image { - border:2px solid #ddd; + border: 2px solid #ddd; } @@ -337,29 +337,29 @@ p.time { .leftbar { h5, .title { - padding:5px 10px; + padding: 5px 10px; } h4 { - font-size:14px; - padding:2px 10px; - color:#666; - border-bottom:1px solid #f1f1f1; + font-size: 14px; + padding: 2px 10px; + color: #666; + border-bottom: 1px solid #f1f1f1; } - a:last-child h4 { border:none; } + a:last-child h4 { border: none; } a:hover { h4 { - color:#111; - background:$hover; - border-color:#CCC; + color: #111; + background: $hover; + border-color: #CCC; .ico.project { background-position:-209px -21px; } } } .bottom { - padding:10px; + padding: 10px; } } @@ -374,12 +374,12 @@ p.time { height: 100%; } .bar-success { + @include linear-gradient(#62C462, #51A351); background-color: #468847; - @include bg-gradient(#62C462, #51A351); } .bar-danger { + @include linear-gradient(#EE5F5B, #BD362F); background-color: #B94A48; - @include bg-gradient(#EE5F5B, #BD362F); } } .upvotes { @@ -420,17 +420,17 @@ p.time { .highlight_word { - background:#EEDC94; + background: #EEDC94; } .status_info { - font-size:14px; - padding:5px 15px; - line-height:24px; - width:60px; - text-align:center; - float:left; - margin-right:20px; + font-size: 14px; + padding: 5px 15px; + line-height: 24px; + width: 60px; + text-align: center; + float: left; + margin-right: 20px; &.success { background: #5BB75B; @@ -449,12 +449,12 @@ p.time { .arrow{ background: #E3E5EA; padding: 5px; - margin-top:5px; - border-radius: 5px; + margin-top: 5px; + @include border-radius(5px); text-shadow: none; color: #999; line-height: 16px; - font-weight:bold; + font-weight: bold; } .thin_area{ @@ -462,19 +462,19 @@ p.time { } .gitlab_pagination { - span a { color:$link_color; } + span a { color: $link_color; } .prev, .next, .current, .page a { - padding:10px; + padding: 10px; } .current { - border-bottom:2px solid $style_color; + border-bottom: 2px solid $style_color; } } // Fixes alignment on notes. .new_note { label { - text-align:left; + text-align: left; } } @@ -486,7 +486,7 @@ li.note { border-bottom:none !important; } .file { - padding-left:20px; + padding-left: 20px; background:url("icon-attachment.png") no-repeat left center; } } @@ -494,7 +494,7 @@ li.note { .markdown { img { - max-width:100%; + max-width: 100%; } } @@ -504,19 +504,19 @@ li.note { .team_member_show { td:first-child { - color:#aaa; + color: #aaa; } } .remember_me { - text-align:left; + text-align: left; input { - margin:0; + margin: 0; } span { - padding-left:5px; + padding-left: 5px; } } @@ -530,10 +530,10 @@ li.note { .data { a { h1 { - line-height:48px; - font-size:48px; - padding:20px; - text-align:center; + line-height: 48px; + font-size: 48px; + padding: 20px; + text-align: center; } } } @@ -541,12 +541,12 @@ li.note { .rss-icon { img { - width:24px; - vertical-align:top; + width: 24px; + vertical-align: top; } strong { - line-height:24px; + line-height: 24px; } } @@ -554,43 +554,43 @@ li.note { /* CHZN reset few styles */ .chzn-container-single .chzn-single { - background:#FFF; + background: #FFF; border: 1px solid #bbb; - box-shadow:none; + box-shadow: none; } .chzn-container-active .chzn-single { - background:#fff; + background: #fff; } .supp_diff_link, .mr_show_all_commits { - cursor:pointer; + cursor: pointer; } .merge_request, .issue { &.today{ background: #EFE; - border-color:#CEC; + border-color: #CEC; } &.closed { background: #F5f5f5; - border-color:#E5E5E5; + border-color: #E5E5E5; } &.merged { background: #F5f5f5; - border-color:#E5E5E5; + border-color: #E5E5E5; } } .git_error_tips { @extend .span6; - text-align:left; - margin-top:40px; + text-align: left; + margin-top: 40px; pre { - background:white; - border:none; + background: white; + border: none; font-size: 12px; } } @@ -602,18 +602,22 @@ li.note { margin-bottom: 10px; background: #FEE; padding-left: 20px; + + &.centered { + text-align: center; + } } .oauth_select_holder { - padding:20px; + padding: 20px; img { - padding:5px; - margin-right:10px; + padding: 5px; + margin-right: 10px; } .active { img { - border:1px solid #ccc; - background:$hover; + border: 1px solid #ccc; + background: $hover; @include border-radius(5px); } } @@ -627,29 +631,49 @@ li.note { .gitlab-promo { a { - color:#aaa; + color: #aaa; margin-right: 30px; } } pre { &.clean { - background:none; - border:none; - margin:0; - padding:0; + background: none; + border: none; + margin: 0; + padding: 0; } } .milestone .progress { margin-bottom: 0; - margin-top:4px; + margin-top: 4px; } .float-link { - float:left; - margin-right:15px; + float: left; + margin-right: 15px; .s16 { - margin-right:5px; + margin-right: 5px; } } + +.dashboard-search-filter { + padding:5px; + + .search-text-input { + float:left; + @extend .span2; + } + .btn { + margin-left: 5px; + float:left; + } +} + +h1.http_status_code { + font-size: 56px; + line-height: 100px; + font-weight: normal; + color: #456; +} diff --git a/app/assets/stylesheets/fonts.scss b/app/assets/stylesheets/fonts.scss new file mode 100644 index 00000000000..88c966d18f7 --- /dev/null +++ b/app/assets/stylesheets/fonts.scss @@ -0,0 +1,7 @@ +@font-face{ + font-family: Korolev; + src: font-url('korolev-medium-compressed.otf'); +} + +/** Typo **/ +$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
\ No newline at end of file diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss index ae66bd201f8..ecd6cf7e4e8 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -15,8 +15,8 @@ @extend .borders; @extend .prepend-top-20; @extend .append-bottom-20; - border-width:1px; - @include solid_shade; + border-width: 1px; + @include solid-shade; img { max-width: 100%; } @@ -30,27 +30,27 @@ .top_box_content, .middle_box_content, .bottom_box_content { - padding:15px; + padding: 15px; pre { background: none !important; - margin:0; - border:none; - padding:0; + margin: 0; + border: none; + padding: 0; } } .middle_box_content { - border-radius:0; - border:none; - font-size:12px; - background-color:#f5f5f5; - border:none; - border-top:1px solid #eee; + @include border-radius(0); + border: none; + font-size: 12px; + background-color: #f5f5f5; + border: none; + border-top: 1px solid #eee; } .bottom_box_content { - border-top:1px solid #eee; + border-top: 1px solid #eee; } } @@ -59,44 +59,48 @@ * */ .ui-box { - background:#F9F9F9; + background: #F9F9F9; margin-bottom: 25px; - @include round-borders-all(4px); + + border: 1px solid #eaeaea; + @include border-radius(4px); + border-color: #CCC; - @include solid_shade; + @include solid-shade; &.white { - background:#fff; + background: #fff; } ul { - margin:0; + margin: 0; } h5, .title { padding: 0 10px; - @include round-borders-top(4px); + @include border-radius(4px 4px 0 0); @include bg-gray-gradient; + border-top: 1px solid #eaeaea; border-bottom: 1px solid #bbb; &.small { line-height: 28px; font-size: 14px; - line-height:28px; + line-height: 28px; text-shadow: 0 1px 1px white; } form { - padding:9px 0; - margin:0px; + padding: 9px 0; + margin: 0px; } .nav-pills { li { - padding:3px 0; - &.active a { background-color:$style_color; } + padding: 3px 0; + &.active a { background-color: $style_color; } a { - border-radius:7px; + @include border-radius(7px); } } } @@ -104,8 +108,8 @@ .bottom { @include bg-gray-gradient; - @include round-borders-bottom(4px); - border-bottom:none; + @include border-radius(0 0 4px 4px); + border-bottom: none; border-top: 1px solid #bbb; } @@ -116,38 +120,38 @@ padding: 5px 20px; } .middle_title { - background:#f5f5f5; + background: #f5f5f5; margin:20px -20px; padding: 0 20px; - border-top:1px solid #eee; - border-bottom:1px solid #eee; - font-size:14px; - color:#777; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; + font-size: 14px; + color: #777; } } .row_title { - font-weight:bold; - color:#444; + font-weight: bold; + color: #444; &:hover { - color:#444; - text-decoration:underline; + color: #444; + text-decoration: underline; } } li, .wll { - padding:10px; + padding: 10px; &:first-child { - @include round-borders-top(4px); - border-top:none; + @include border-radius(4px 4px 0 0); + border-top: none; } &:last-child { - @include round-borders-bottom(4px); - border:none; + @include border-radius(0 0 4px 4px); + border: none; } } .ui-box-body { - padding:10px; + padding: 10px; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss index f9249e871b7..883a8773962 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss @@ -1,42 +1,42 @@ .btn { - @include bg-gradient(#f7f7f7, #d5d5d5); - border-color:#aaa; + @include linear-gradient(#f7f7f7, #d5d5d5); + border-color: #aaa; &:hover { @include bg-gray-gradient; - border-color:#bbb; - color:#333; + border-color: #bbb; + color: #333; } &.primary { - background:#2a79A3; - @include bg-gradient(#47A7b7, #2585b5); + background: #2a79A3; + @include linear-gradient(#47A7b7, #2585b5); border-color: #2A79A3; - color:#fff; + color: #fff; text-shadow: 0 1px 1px #268; &:hover { - background:$blue_link; - color:#fff; + background: $primary_color; + color: #fff; } &.disabled { - color:#fff; - background:#29B; + color: #fff; + background: #29B; } } &.btn-info { - background:#5aB9C3; - border-color: $blue_link; - color:#fff; + background: #5aB9C3; + border-color: $primary_color; + color: #fff; text-shadow: 0 1px 1px #268; &:hover { - background:$blue_link; - color:#fff; + background: $primary_color; + color: #fff; } &.disabled { - color:#fff; - background:#29B; + color: #fff; + background: #29B; } } @@ -49,8 +49,8 @@ } &.disabled { - color:#fff; - background:#2b2; + color: #fff; + background: #2b2; } } @@ -60,12 +60,12 @@ } &.cancel-btn { - float:right; + float: right; } &.wide { - padding-left:30px; - padding-right:30px; + padding-left: 30px; + padding-right: 30px; } &.danger { @@ -73,7 +73,7 @@ border-color: #BD362F; &:hover { - color:#fff; + color: #fff; background: #EE4E49; } } @@ -87,24 +87,24 @@ } &.active { - border-color:#aaa; - background-color:#ccc; + border-color: #aaa; + background-color: #ccc; } &.very_small { - font-size:11px; - padding:2px 6px; + font-size: 11px; + padding: 2px 6px; line-height: 16px; - margin:2px; + margin: 2px; } &.grouped { - margin-right:7px; - float:left; + margin-right: 7px; + float: left; } &.padded { - margin-right:3px; - padding:4px 10px 4px; + margin-right: 3px; + padding: 4px 10px 4px; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index c1b2880140d..9a4f2e80f87 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -24,7 +24,7 @@ .lborder { border-left:1px solid #eee } .no-padding { padding:0 !important; } .underlined { border-bottom: 1px solid #CCC; } -.no-borders { border:none; } +.no-borders { border: none; } .vlink { color: $link_color !important; } .underlined_link { text-decoration: underline; } .borders { border: 1px solid #ccc; @include shade; } @@ -32,32 +32,33 @@ .light { color: #888 } /** PILLS & TABS**/ -.nav-pills a:hover { background-color:#888; } +.nav-pills a:hover { background-color: #888; } .nav-pills .active a { background-color: $style_color; } -.nav-tabs > li > a, .nav-pills > li > a { color:$style_color; } +.nav-pills > .active > a > i[class^="icon-"] { background: inherit; } +.nav-tabs > li > a, .nav-pills > li > a { color: $style_color; } .nav.nav-tabs { li { > a { - padding:8px 20px; + padding: 8px 20px; margin-right: 7px; line-height: 19px; border-color: #EEE; - color:#888; + color: #888; border-bottom: 1px solid #ddd; .badge { background-color: #eee; - color:#888; - text-shadow:0 1px 1px #fff; + color: #888; + text-shadow: 0 1px 1px #fff; } i[class^="icon-"] { - line-height:14px; + line-height: 14px; } } &.active { > a { border-color: #CCC; border-bottom: 1px solid #fff; - color:#333; + color: #333; } } } @@ -69,25 +70,47 @@ .alert-message.error { @extend .alert-error; } /** AVATARS **/ -img.avatar { float:left; margin-right:12px; width:40px; border:1px solid #ddd; padding:1px; } -img.avatar.s16 { width:16px; height:16px; margin-right:6px; } -img.avatar.s24 { width:24px; height:24px; margin-right:8px; } -img.avatar.s32 { width:32px; height:32px; margin-right:10px; } -img.lil_av { padding-left: 4px; padding-right:3px; } +img.avatar { float: left; margin-right: 12px; width: 40px; border: 1px solid #ddd; padding: 1px; } +img.avatar.s16 { width: 16px; height: 16px; margin-right: 6px; } +img.avatar.s24 { width: 24px; height: 24px; margin-right: 8px; } +img.avatar.s32 { width: 32px; height: 32px; margin-right: 10px; } +img.lil_av { padding-left: 4px; padding-right: 3px; } img.small { width: 80px; } /** HELPERS **/ -.nothing_here_message { text-align:center; padding:20px; color:#777; } -p.slead { color:#456; font-size:16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; } +.nothing_here_message { + text-align: center; + padding: 20px; + color: #666; + font-weight: normal; + font-size: 16px; + line-height: 36px; +} + +p.slead { color: #456; font-size: 16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; } /** FORMS **/ input[type='search'].search-text-input { background-image: url("icon-search.png"); background-repeat: no-repeat; background-position: 10px; - padding-left:25px; + padding-left: 25px; @include border-radius(4px); - border:1px solid #ccc; + border: 1px solid #ccc; } fieldset legend { font-size: 17px; } + +ul.nav.nav-projects-tabs { + @extend .nav-tabs; + + padding-left: 8px; + + li { + a { + padding: 4px 20px; + margin-top: 2px; + border-color: #DDD; + } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss index 4887d1c9402..e4924a49456 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/files.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss @@ -3,9 +3,9 @@ * */ .file_holder { - border:1px solid #BBB; - margin-bottom:1em; - @include solid_shade; + border: 1px solid #BBB; + margin-bottom: 1em; + @include solid-shade; .file_title { border-bottom: 1px solid #bbb; @@ -16,33 +16,33 @@ text-align: left; color: #666; padding: 9px 10px; - height:18px; + height: 18px; .options { - float:right; + float: right; margin-top: -5px; } .file_name { - color:$style_color; - font-size:14px; + color: $style_color; + font-size: 14px; text-shadow: 0 1px 1px #fff; small { - color:#999; - font-size:13px; + color: #999; + font-size: 13px; } } } .file_content { - background:#fff; + background: #fff; font-size: 11px; &.wiki { font-size: 13px; code { - padding:0 4px; + padding: 0 4px; } - padding:20px; + padding: 20px; h1, h2 { line-height: 46px; } @@ -52,11 +52,11 @@ } &.image_file { - background:#eee; - text-align:center; + background: #eee; + text-align: center; img { - padding:100px; - max-width:300px; + padding: 100px; + max-width: 300px; } } @@ -69,60 +69,60 @@ */ &.blame { table { - border:none; - box-shadow:none; - margin:0; + border: none; + box-shadow: none; + margin: 0; } tr { border-bottom: 1px solid #eee; } td { &:first-child { - border-left:none; + border-left: none; } &:last-child { - border-right:none; + border-right: none; } - background:#fff; - padding:5px; + background: #fff; + padding: 5px; } .author, .blame_commit { - background:#f5f5f5; - vertical-align:top; + background: #f5f5f5; + vertical-align: top; } .lines { pre { - padding:0; - margin:0; - background:none; - border:none; + padding: 0; + margin: 0; + background: none; + border: none; } } } &.logs { - background:#eee; + background: #eee; max-height: 700px; overflow-y: auto; ol { - margin-left:40px; + margin-left: 40px; padding: 10px 0; border-left: 1px solid #CCC; - margin-bottom:0; + margin-bottom: 0; background: white; li { - color:#888; + color: #888; p { - margin:0; - color:#333; - line-height:24px; + margin: 0; + color: #333; + line-height: 24px; padding-left: 10px; } &:hover { - background:$hover; + background: $hover; } } } @@ -142,8 +142,8 @@ table-layout: fixed; pre { - background: none; border: none; + border-radius: 0; font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-size: 12px !important; line-height: 16px !important; diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss index 4fe45ecc277..5bd087b080e 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/lists.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss @@ -12,21 +12,21 @@ ul { border-bottom: 1px solid #eee; border-bottom: 1px solid rgba(0, 0, 0, 0.05); - &.smoke { background-color:#f5f5f5; } + &.smoke { background-color: #f5f5f5; } &:hover { - background:$hover; - border-bottom:1px solid #ADF; + background: $hover; + border-bottom: 1px solid #ADF; } &:last-child { border:none } .author { color: #999; } p { padding-top: 1px; - margin:0; - color:#222; + margin: 0; + color: #222; img { - position:relative; - top:3px; + position: relative; + top: 3px; } } } @@ -35,7 +35,7 @@ ul { ol, ul { &.styled { li { - padding:2px; + padding: 2px; } } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/tables.scss b/app/assets/stylesheets/gitlab_bootstrap/tables.scss index 549cdfee5a6..5905efd3aae 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/tables.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/tables.scss @@ -1,13 +1,13 @@ table { @extend .table; @extend .table-striped; - @include solid_shade; - border:1px solid #bbb; - width:100%; + @include solid-shade; + border: 1px solid #bbb; + width: 100%; &.low { td { - line-height:18px; + line-height: 18px; } } @@ -31,8 +31,8 @@ table { } td { - border-color:#f1f1f1; - line-height:28px; + border-color: #f1f1f1; + line-height: 28px; .s16 { margin-top: 5px; @@ -40,11 +40,11 @@ table { } &:first-child { - border-left:1px solid #bbb; + border-left: 1px solid #bbb; } &:last-child { - border-right:1px solid #bbb; + border-right: 1px solid #bbb; } } @@ -53,10 +53,10 @@ table { } &.lite { - border:none; - box-shadow:none; + border: none; + box-shadow: none; tr, td { - border:none; + border: none; background:none !important; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/gitlab_bootstrap/typography.scss index fe3bd68b608..81fb79a43f2 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/typography.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/typography.scss @@ -5,11 +5,11 @@ h1, h2, h3, h4, h5, h6 { margin: 0; } h3, h4, h5, h6 { line-height: 36px; } -h5 { font-size:14px; } +h5 { font-size: 14px; } h3.page_title { - color:#456; - font-size:20px; + color: #456; + font-size: 20px; font-weight: normal; line-height: 28px; } @@ -25,7 +25,7 @@ pre { &.dark { background: #333; - color:#f5f5f5; + color: #f5f5f5; } } @@ -37,8 +37,8 @@ a { outline: none; color: $link_color; &:hover { - text-decoration:none; - color: $blue_link; + text-decoration: none; + color: $primary_color; } &.btn { @@ -53,27 +53,31 @@ a { } &.lined { - text-decoration:underline; - &:hover { text-decoration:underline; } + text-decoration: underline; + &:hover { text-decoration: underline; } } &.gray { - color:gray; + color: gray; } &.supp_diff_link { - text-align:center; - padding:20px 0; - background:#f1f1f1; - width:100%; - float:left; + text-align: center; + padding: 20px 0; + background: #f1f1f1; + width: 100%; + float: left; } &.neib { - margin-right:15px; + margin-right: 15px; } } a:focus { outline: none; } + +.monospace { + font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; +} diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 0996cc772f4..4196ea7ad29 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,6 +1,8 @@ -.black .lines .highlight { - background: #333; - pre { color: #eee; } +.black .highlight { + pre { + background-color: #333; + color: #eee; + } .hll { display: block; background-color: darken($hover, 65%) } .c { color: #888888; font-style: italic } /* Comment */ @@ -21,43 +23,43 @@ .gs { font-weight: bold } /* Generic.Strong */ .gu { color: #606060 } /* Generic.Subheading */ .gt { color: #aa0000 } /* Generic.Traceback */ - .kc{font-weight:bold;} /* Keyword.Constant */ - .kd{font-weight:bold;} /* Keyword.Declaration */ - .kn{font-weight:bold;} /* Keyword.Namespace */ - .kp{font-weight:bold;} /* Keyword.Pseudo */ - .kr{font-weight:bold;} /* Keyword.Reserved */ - .kt{color:#458;font-weight:bold;} /* Keyword.Type */ + .kc{font-weight: bold;} /* Keyword.Constant */ + .kd{font-weight: bold;} /* Keyword.Declaration */ + .kn{font-weight: bold;} /* Keyword.Namespace */ + .kp{font-weight: bold;} /* Keyword.Pseudo */ + .kr{font-weight: bold;} /* Keyword.Reserved */ + .kt{color: #458;font-weight: bold;} /* Keyword.Type */ .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .p { color: #eee; } .s { color: #0AD; background-color: transparent } /* Literal.String */ - .na{color:#008080;} /* Name.Attribute */ - .nb{color:#0086B3;} /* Name.Builtin */ - .nc{color:#ccc;font-weight:bold;} /* Name.Class */ - .no{color:turquoise;} /* Name.Constant */ - .ni{color:#800080;} - .ne{color:#900;font-weight:bold;} /* Name.Exception */ - .nf{color:#ccc;font-weight:bold;} /* Name.Function */ - .nn{color:#79C3E0;font-weight:bold;} /* Name.Namespace */ - .nt{color:#fc5;} /* Name.Tag */ - .nv{color:#FA4;} /* Name.Variable */ + .na{color: #008080;} /* Name.Attribute */ + .nb{color: #0086B3;} /* Name.Builtin */ + .nc{color: #ccc;font-weight: bold;} /* Name.Class */ + .no{color: turquoise;} /* Name.Constant */ + .ni{color: #800080;} + .ne{color: #900;font-weight: bold;} /* Name.Exception */ + .nf{color: #ccc;font-weight: bold;} /* Name.Function */ + .nn{color: #79C3E0;font-weight: bold;} /* Name.Namespace */ + .nt{color: #fc5;} /* Name.Tag */ + .nv{color: #FA4;} /* Name.Variable */ .py { color: #336699; font-weight: bold } /* Name.Property */ .ow { color: #008800 } /* Operator.Word */ .w { color: #bbbbbb } /* Text.Whitespace */ .mf { color: #7AC; font-weight: bold } /* Literal.Number.Float */ .mh { color: #7AC; font-weight: bold } /* Literal.Number.Hex */ - .mi {color:#099;} /* Literal.Number.Integer */ + .mi {color: #099;} /* Literal.Number.Integer */ .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .sb { color: #dd2200; background-color: transparent; } /* Literal.String.Backtick */ - .sc{color:#d14;} /* Literal.String.Char */ + .sc{color: #d14;} /* Literal.String.Char */ .sd { color: #dd2200; background-color: transparent; } /* Literal.String.Doc */ - .s2{color:orange;} /* Literal.String.Double */ - .se{color:orange;} /* Literal.String.Escape */ - .sh{color:orange;} /* Literal.String.Heredoc */ - .si{color:orange;} /* Literal.String.Interpol */ - .sx{color:orange;} /* Literal.String.Other */ - .sr{color:orange;} /* Literal.String.Regex */ - .s1{color:orange;} /* Literal.String.Single */ - .ss{color:orange;} /* Literal.String.Symbol */ + .s2{color: orange;} /* Literal.String.Double */ + .se{color: orange;} /* Literal.String.Escape */ + .sh{color: orange;} /* Literal.String.Heredoc */ + .si{color: orange;} /* Literal.String.Interpol */ + .sx{color: orange;} /* Literal.String.Other */ + .sr{color: orange;} /* Literal.String.Regex */ + .s1{color: orange;} /* Literal.String.Single */ + .ss{color: orange;} /* Literal.String.Symbol */ .bp { color: #D58 } /* Name.Builtin.Pseudo */ .vc { color: #336699 } /* Name.Variable.Class */ .vg { color: #dd7700 } /* Name.Variable.Global */ diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index d6792b37e9c..f200e1d7b60 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,6 +1,8 @@ -.white .lines .highlight { - background: white; - pre { color: #333; } +.white .highlight { + pre { + background-color: #fff; + color: #333; + } .hll { display: block; background-color: $hover } .c { color: #888888; font-style: italic } /* Comment */ @@ -20,42 +22,42 @@ .gs { font-weight: bold } /* Generic.Strong */ .gu { color: #606060 } /* Generic.Subheading */ .gt { color: #aa0000 } /* Generic.Traceback */ - .kc{font-weight:bold;} /* Keyword.Constant */ - .kd{font-weight:bold;} /* Keyword.Declaration */ - .kn{font-weight:bold;} /* Keyword.Namespace */ - .kp{font-weight:bold;} /* Keyword.Pseudo */ - .kr{font-weight:bold;} /* Keyword.Reserved */ - .kt{color:#458;font-weight:bold;} /* Keyword.Type */ + .kc{font-weight: bold;} /* Keyword.Constant */ + .kd{font-weight: bold;} /* Keyword.Declaration */ + .kn{font-weight: bold;} /* Keyword.Namespace */ + .kp{font-weight: bold;} /* Keyword.Pseudo */ + .kr{font-weight: bold;} /* Keyword.Reserved */ + .kt{color: #458;font-weight: bold;} /* Keyword.Type */ .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ - .na{color:#008080;} /* Name.Attribute */ - .nb{color:#0086B3;} /* Name.Builtin */ - .nc{color:#458;font-weight:bold;} /* Name.Class */ - .no{color:#008080;} /* Name.Constant */ - .ni{color:#800080;} - .ne{color:#900;font-weight:bold;} /* Name.Exception */ - .nf{color:#900;font-weight:bold;} /* Name.Function */ - .nn{color:#005;font-weight:bold;} /* Name.Namespace */ - .nt{color:#000080;} /* Name.Tag */ - .nv{color:#008080;} /* Name.Variable */ + .na{color: #008080;} /* Name.Attribute */ + .nb{color: #0086B3;} /* Name.Builtin */ + .nc{color: #458;font-weight: bold;} /* Name.Class */ + .no{color: #008080;} /* Name.Constant */ + .ni{color: #800080;} + .ne{color: #900;font-weight: bold;} /* Name.Exception */ + .nf{color: #900;font-weight: bold;} /* Name.Function */ + .nn{color: #005;font-weight: bold;} /* Name.Namespace */ + .nt{color: #000080;} /* Name.Tag */ + .nv{color: #008080;} /* Name.Variable */ .py { color: #336699; font-weight: bold } /* Name.Property */ .ow { color: #008800 } /* Operator.Word */ .w { color: #bbbbbb } /* Text.Whitespace */ .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ - .mi {color:#099;} /* Literal.Number.Integer */ + .mi {color: #099;} /* Literal.Number.Integer */ .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ - .sc{color:#d14;} /* Literal.String.Char */ + .sc{color: #d14;} /* Literal.String.Char */ .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ - .s2{color:#d14;} /* Literal.String.Double */ - .se{color:#d14;} /* Literal.String.Escape */ - .sh{color:#d14;} /* Literal.String.Heredoc */ - .si{color:#d14;} /* Literal.String.Interpol */ - .sx{color:#d14;} /* Literal.String.Other */ - .sr{color:#d14;} /* Literal.String.Regex */ - .s1{color:#d14;} /* Literal.String.Single */ - .ss{color:#d14;} /* Literal.String.Symbol */ + .s2{color: #d14;} /* Literal.String.Double */ + .se{color: #d14;} /* Literal.String.Escape */ + .sh{color: #d14;} /* Literal.String.Heredoc */ + .si{color: #d14;} /* Literal.String.Interpol */ + .sx{color: #d14;} /* Literal.String.Other */ + .sr{color: #d14;} /* Literal.String.Regex */ + .s1{color: #d14;} /* Literal.String.Single */ + .ss{color: #d14;} /* Literal.String.Symbol */ .bp { color: #003388 } /* Name.Builtin.Pseudo */ .vc { color: #336699 } /* Name.Variable.Class */ .vg { color: #dd7700 } /* Name.Variable.Global */ @@ -63,7 +65,5 @@ } .shadow { - -webkit-box-shadow:0 5px 15px #000; - -moz-box-shadow:0 5px 15px #000; - box-shadow:0 5px 15px #000; + @include box-shadow(0 5px 15px #000); } diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index 7ae32b3a7d9..bc7a74406ef 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -2,96 +2,31 @@ $baseFontSize: 13px !default; $baseLineHeight: 18px !default; +// BOOTSTRAP @import "bootstrap"; -@import "bootstrap-responsive"; -@import 'font-awesome'; - -/** GitLab colors **/ -$link_color: #3A89A3; -$blue_link: #2FA0BB; -$style_color: #474D57; -$hover: #D9EDF7; -$hover_border: #ADF; - -/** GitLab Fonts **/ -@font-face { font-family: Korolev; src: font-url('korolev-medium-compressed.otf'); } -$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; - -/** MIXINS **/ -@mixin shade { - -moz-box-shadow: 0 0 3px #ddd; - -webkit-box-shadow: 0 0 3px #ddd; - box-shadow: 0 0 3px #ddd; -} - -@mixin solid_shade { - -moz-box-shadow: 0 0 0 3px #f1f1f1; - -webkit-box-shadow: 0 0 0 3px #f1f1f1; - box-shadow: 0 0 0 3px #f1f1f1; -} - -@mixin border-radius($radius) { - -moz-border-radius: $radius; - -webkit-border-radius: $radius; - border-radius: $radius; -} - -@mixin round-borders-bottom($radius) { - border-top: 1px solid #eaeaea; - -moz-border-radius-bottomright: $radius; - -moz-border-radius-bottomleft: $radius; - border-bottom-right-radius: $radius; - border-bottom-left-radius: $radius; - -webkit-border-bottom-left-radius: $radius; - -webkit-border-bottom-right-radius: $radius; -} - -@mixin round-borders-top($radius) { - border-top: 1px solid #eaeaea; - -moz-border-radius-topright: $radius; - -moz-border-radius-topleft: $radius; - border-top-right-radius: $radius; - border-top-left-radius: $radius; - -webkit-border-top-left-radius: $radius; - -webkit-border-top-right-radius: $radius; -} - -@mixin round-borders-all($radius) { - border: 1px solid #eaeaea; - -moz-border-radius: $radius; - -webkit-border-radius: $radius; - border-radius: $radius; -} - -@mixin bg-gradient($from, $to) { - background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); - background-image: -webkit-linear-gradient($from, $to); - background-image: -moz-linear-gradient($from, $to); - background-image: -o-linear-gradient($from, $to); -} - -@mixin bg-light-gray-gradient { - background:#f1f1f1; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #f5f5f5), to(#e1e1e1)); - background-image: -webkit-linear-gradient(#f5f5f5 6.6%, #e1e1e1); - background-image: -moz-linear-gradient(#f5f5f5 6.6%, #e1e1e1); - background-image: -o-linear-gradient(#f5f5f5 6.6%, #e1e1e1); -} - -@mixin bg-gray-gradient { - background:#eee; - 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: -o-linear-gradient(#eee 6.6%, #dfdfdf); -} - -@mixin bg-dark-gray-gradient { - background:#eee; - background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7); - background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7); - background-image: -o-linear-gradient(#e9e9e9, #d7d7d7); -} +@import "bootstrap/responsive-utilities"; +@import "bootstrap/responsive-1200px-min"; + +// FONT AWESOME +@import "font-awesome"; + +/** + * Variables + * Contains colors + */ +@import "variables.scss"; + +/** + * Custom fonts + * Contains @font-face font Korolev and default $monotype + */ +@import "fonts.scss"; + +/** + * General mixins. + * Contains rounded borders, gradients and shades + */ +@import "mixins.scss"; /** * Header of application. diff --git a/app/assets/stylesheets/mixins.scss b/app/assets/stylesheets/mixins.scss new file mode 100644 index 00000000000..441a85f3410 --- /dev/null +++ b/app/assets/stylesheets/mixins.scss @@ -0,0 +1,60 @@ +/** + * Generic mixins + */ + @mixin box-shadow($shadow) { + -webkit-box-shadow: $shadow; + -moz-box-shadow: $shadow; + -ms-box-shadow: $shadow; + -o-box-shadow: $shadow; + box-shadow: $shadow; +} + +@mixin border-radius($radius) { + -webkit-border-radius: $radius; + -moz-border-radius: $radius; + -ms-border-radius: $radius; + -o-border-radius: $radius; + border-radius: $radius; +} + +@mixin linear-gradient($from, $to) { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); + background-image: -webkit-linear-gradient($from, $to); + background-image: -moz-linear-gradient($from, $to); + background-image: -o-linear-gradient($from, $to); +} + +/** + * Prefilled mixins + * Mixins with fixed values + */ +@mixin bg-light-gray-gradient { + background: #f1f1f1; + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #f5f5f5), to(#e1e1e1)); + background-image: -webkit-linear-gradient(#f5f5f5 6.6%, #e1e1e1); + background-image: -moz-linear-gradient(#f5f5f5 6.6%, #e1e1e1); + background-image: -o-linear-gradient(#f5f5f5 6.6%, #e1e1e1); +} + +@mixin bg-gray-gradient { + background: #eee; + 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: -o-linear-gradient(#eee 6.6%, #dfdfdf); +} + +@mixin bg-dark-gray-gradient { + background: #eee; + background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -o-linear-gradient(#e9e9e9, #d7d7d7); +} + +@mixin shade { + @include box-shadow(0 0 3px #ddd); +} + +@mixin solid-shade { + @include box-shadow(0 0 0 3px #f1f1f1); +}
\ No newline at end of file diff --git a/app/assets/stylesheets/ref_select.scss b/app/assets/stylesheets/ref_select.scss index 377d008605e..284d1c324e5 100644 --- a/app/assets/stylesheets/ref_select.scss +++ b/app/assets/stylesheets/ref_select.scss @@ -1,6 +1,6 @@ /** Branch/tag selector **/ .project-refs-form { - margin:0; + margin: 0; span { background:none !important; position:static !important; @@ -9,7 +9,7 @@ } } .project-refs-select { - width:120px; + width: 120px; } .project-refs-form .chzn-container { @@ -21,10 +21,10 @@ .chzn-drop { min-width: 400px; .chzn-results { - max-height:300px; + max-height: 300px; } .chzn-search input { - min-width:365px; + min-width: 365px; } } } @@ -33,21 +33,19 @@ .chzn-container { .chzn-search { input:focus { - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; + @include box-shadow(none); } } .chzn-drop { - margin:7px 0; + margin: 7px 0; min-width: 200px; border: 1px solid #bbb; - border-radius:0; + @include border-radius(0); .chzn-results { margin-top: 5px; - max-height:300px; + max-height: 300px; .group-result { color: $style_color; @@ -55,7 +53,7 @@ padding: 8px; } .active-result { - border-radius: 0; + @include border-radius(0); &.highlighted { background: $hover; @@ -71,7 +69,7 @@ .chzn-search { @include bg-gray-gradient; input { - min-width:165px; + min-width: 165px; border-color: #CCC; } } @@ -81,8 +79,8 @@ @include bg-light-gray-gradient; div { - background:transparent; - border-left:none; + background: transparent; + border-left: none; } span { diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index 9cce5bd0389..bf405bfcd51 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -6,14 +6,14 @@ .commit-title { line-height: 26px; - margin:0; + margin: 0; } .commit-description { font-size: 14px; border: none; background-color: white; - padding-top:10px; + padding-top: 10px; } .browse-button { @@ -28,9 +28,9 @@ @extend .clearfix; .sha-block { - text-align:right; + text-align: right; &:first-child { - padding-bottom:6px; + padding-bottom: 6px; } a { @@ -49,10 +49,10 @@ .author a, .committer a { - font-size:14px; - line-height:22px; - text-shadow:0 1px 1px #fff; - color:#777; + font-size: 14px; + line-height: 22px; + text-shadow: 0 1px 1px #fff; + color: #777; &:hover { color: #999; } @@ -70,15 +70,16 @@ * */ .diff_file { - border:1px solid #CCC; - margin-bottom:1em; + border: 1px solid #CCC; + margin-bottom: 1em; .diff_file_header { @extend .clearfix; padding: 5px 5px 5px 10px; color: #555; - border-bottom:1px solid #CCC; + border-bottom: 1px solid #CCC; background: #eee; + // TODO Replace with linear-gradient mixin 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); @@ -86,7 +87,7 @@ > span { font-family: $monospace; - font-size:14px; + font-size: 14px; line-height: 30px; } @@ -104,36 +105,36 @@ } } .diff_file_content { - overflow:auto; - overflow-y:hidden; - background:#fff; - color:#333; + overflow: auto; + overflow-y: hidden; + background: #fff; + color: #333; font-size: 12px; font-family: $monospace; .old{ span.idiff{ - background-color:#FAA; + background-color: #FAA; } } .new{ span.idiff{ - background-color:#AFA; + background-color: #AFA; } } table { td { - line-height:18px; + line-height: 18px; } } } .diff_file_content_image { - background:#eee; - text-align:center; + background: #eee; + text-align: center; .image { display: inline-block; - margin:50px; - max-width:400px; + margin: 50px; + max-width: 400px; img{ background: url('trans_bg.gif'); @@ -158,7 +159,7 @@ &.img_compared { .image { - max-width:300px; + max-width: 300px; } } } @@ -166,46 +167,46 @@ .diff_file_content{ table { - border:none; - margin:0px; - padding:0px; + border: none; + margin: 0px; + padding: 0px; tr { td { - font-size:12px; + font-size: 12px; } } } .old_line, .new_line { - margin:0px; - padding:0px; - border:none; - background:#EEE; - color:#666; + 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:35px; - width:35px; + text-align: right; + min-width: 35px; + max-width: 35px; + width: 35px; moz-user-select: none; -khtml-user-select: none; user-select: none; a { - float:left; - width:35px; - font-weight:normal; - color:#666; + float: left; + width: 35px; + font-weight: normal; + color: #666; &:hover { - text-decoration:underline; + text-decoration: underline; } } } .line_content { - white-space:pre; - height:14px; - margin:0px; - padding:0px; - border:none; + white-space: pre; + height: 14px; + margin: 0px; + padding: 0px; + border: none; &.new { background: #CFD; } @@ -213,8 +214,8 @@ background: #FDD; } &.matched { - color:#ccc; - background:#fafafa; + color: #ccc; + background: #fafafa; } } } @@ -232,28 +233,28 @@ .browse_code_link_holder { @extend .span2; - float:right; + float: right; } .committed_ago { - float:right; + float: right; @extend .cgray; } .notes_count { - float:right; + float: right; margin: -6px 8px 6px; } code { - background:#FCEEC1; - color:$style_color; + background: #FCEEC1; + color: $style_color; } .commit_short_id { - float:left; + float: left; @extend .lined; - min-width:65px; + min-width: 65px; font-family: $monospace; } @@ -294,11 +295,10 @@ } .label_commit { - @include round-borders-all(4px); - padding:2px 4px; - border:none; - font-size:13px; + @include border-radius(4px); + padding: 2px 4px; + font-size: 13px; background: #474D57; - color:#fff; + color: #fff; font-family: $monospace; } diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/sections/editor.scss index 711dc0d8e43..a71e5438936 100644 --- a/app/assets/stylesheets/sections/editor.scss +++ b/app/assets/stylesheets/sections/editor.scss @@ -1,7 +1,7 @@ .file-editor { #editor{ border: none; - border-radius: 0; + @include border-radius(0); height: 500px; margin: 0; padding: 0; diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index 369ebc81e31..28551d9a6ee 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -4,25 +4,25 @@ */ .event_label { &.pushed { - padding:0 2px; + padding: 0 2px; } &.opened { - padding:0 2px; + padding: 0 2px; } &.closed { - padding:0 2px; + padding: 0 2px; } &.merged { - padding:0 2px; + padding: 0 2px; } &.left, &.joined { - padding:0 2px; - float:none; + padding: 0 2px; + float: none; } } @@ -31,26 +31,26 @@ * */ .event-item { - min-height:40px; - border-bottom:1px solid #eee; + min-height: 40px; + border-bottom: 1px solid #eee; .event-title { - color:#333; + color: #333; font-weight: bold; .author_name { - color:#333; + color: #333; } } .event-body { p { - color:#555; + color: #555; padding-top: 5px; } .event-info { - color:#666; + color: #666; } } .avatar { - width:32px; + width: 32px; } .event_icon { float: right; @@ -59,15 +59,15 @@ @include border-radius(5px); background: #F9F9F9; img { - width:20px; + width: 20px; } } ul { - margin-left:50px; - margin-bottom:5px; + margin-left: 50px; + margin-bottom: 5px; .avatar { - width:18px; - margin-top:3px; + width: 18px; + margin-top: 3px; } } @@ -81,9 +81,9 @@ li { &.commit { background: transparent; - padding:3px; - border:none; - font-size:12px; + padding: 3px; + border: none; + font-size: 12px; } &.commits-stat { display: block; @@ -98,15 +98,15 @@ * */ .event_lp { - color:#777; - padding:10px; - min-height:22px; + color: #777; + padding: 10px; + min-height: 22px; border-left: 5px solid #5AB9C3; - margin-bottom:20px; - background:#f9f9f9; + margin-bottom: 20px; + background: #f9f9f9; .avatar { - width:24px; + width: 24px; } .btn-new-mr { @@ -133,7 +133,7 @@ background: #f9f9f9; margin-bottom: 10px; img { - width:20px; + width: 20px; } &.inactive { diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss index 2aa4463e45e..5800098ade4 100644 --- a/app/assets/stylesheets/sections/graph.scss +++ b/app/assets/stylesheets/sections/graph.scss @@ -1,10 +1,10 @@ .graph_holder { border: 1px solid #aaa; - padding:1px; + padding: 1px; h4 { - padding:0 10px; + padding: 0 10px; border-bottom: 1px solid #bbb; @include bg-gray-gradient; } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index 0db40ec9ac7..4171c00ab8b 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -5,7 +5,7 @@ header { &.navbar-gitlab { .navbar-inner { - height:45px; + height: 45px; padding: 5px; background: #F1F1F1; @@ -24,8 +24,8 @@ header { } } - z-index:10; - /*height:60px;*/ + z-index: 10; + /*height: 60px;*/ /** * @@ -33,24 +33,24 @@ header { * */ .app_logo { - width:170px; - float:left; + width: 170px; + float: left; a { - float:left; + float: left; padding: 0px; h1 { - width:90px; + width: 90px; background: url('logo_dark.png') no-repeat 0px 2px; - float:left; - margin-left:2px; - font-size:30px; - line-height:48px; - font-weight:normal; - color:$style_color; + float: left; + margin-left: 2px; + font-size: 30px; + line-height: 48px; + font-weight: normal; + color: $style_color; text-shadow: 0 1px 1px #FFF; - padding-left:45px; - height:40px; + padding-left: 45px; + height: 40px; font-family: 'Korolev', sans-serif; } } @@ -62,14 +62,14 @@ header { * */ .project_name { - position:relative; - float:left; - margin:0; - margin-right:30px; - font-size:30px; - line-height:48px; - font-weight:normal; - color:$style_color; + position: relative; + float: left; + margin: 0; + margin-right: 30px; + font-size: 30px; + line-height: 48px; + font-weight: normal; + color: $style_color; text-shadow: 0 1px 1px #FFF; font-family: 'Korolev', sans-serif; } @@ -81,7 +81,7 @@ header { */ .search { margin-right: 45px; - margin-left:10px; + margin-left: 10px; margin-top: 2px; .search-input { @@ -89,11 +89,11 @@ header { background-image: url("icon-search.png"); background-repeat: no-repeat; background-position: 10px; - padding-left:25px; + padding-left: 25px; font-size: 13px; @include border-radius(3px); - border:1px solid #c6c6c6; - box-shadow:none; + border: 1px solid #c6c6c6; + box-shadow: none; &:focus { @extend .span3; } @@ -122,7 +122,7 @@ header { width: 28px; height: 28px; display: block; - top:1px; + top: 1px; &:after { content: " "; display: block; @@ -132,12 +132,15 @@ header { left: 0; bottom: 0; float: right; - border-radius: 5px; + @include border-radius(5px); border: 1px solid rgba(255, 255, 255, 0.1); border-bottom: 0; - background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255, 255, 255, 0.15)), to(rgba(0, 0, 0, 0.25))), -webkit-gradient(linear, left top, right bottom, color-stop(0, rgba(255, 255, 255, 0)), color-stop(0.5, rgba(255, 255, 255, 0.1)), color-stop(0.501, rgba(255, 255, 255, 0)), color-stop(1, rgba(255, 255, 255, 0))); - background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)), -moz-linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0)); - background: linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)), linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0)); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255, 255, 255, 0.15)), to(rgba(0, 0, 0, 0.25))), + -webkit-gradient(linear, left top, right bottom, color-stop(0, rgba(255, 255, 255, 0)), color-stop(0.5, rgba(255, 255, 255, 0.1)), color-stop(0.501, rgba(255, 255, 255, 0)), color-stop(1, rgba(255, 255, 255, 0))); + background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)), + -moz-linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0)); + background: linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)), + linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0)); -webkit-background-origin: border-box; -moz-background-origin: border; background-origin: border-box; } } } @@ -149,7 +152,7 @@ header { display: block; } } .account-links { - border-radius: 5px; + @include border-radius(5px); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); position: relative; &:before { @@ -184,7 +187,7 @@ header { border-bottom: 1px solid #666; font-size: 12px; &:hover { - color:#fff; + color: #fff; background: #333; } } @@ -197,20 +200,13 @@ header { .account-links a { &:first-child { - -webkit-border-top-left-radius: 5px; - -webkit-border-top-right-radius: 5px; - -moz-border-radius-topleft: 5px; - -moz-border-radius-topright: 5px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; } + @include border-radius(5px 5px 0 0); + } &:last-child { - -webkit-border-bottom-right-radius: 5px; - -webkit-border-bottom-left-radius: 5px; - -moz-border-radius-bottomright: 5px; - -moz-border-radius-bottomleft: 5px; - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; - border-bottom: 0; } } + @include border-radius(0 0 5px 5px); + border-bottom: 0; + } + } @@ -248,13 +244,13 @@ header { a { h1 { background: url('logo_white.png') no-repeat 0px 2px; - color:#fff; + color: #fff; text-shadow: 0 1px 1px #111; } } } .project_name { - color:#fff; + color: #fff; text-shadow: 0 1px 1px #111; } } diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 93622d6149a..ca1aab44916 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -3,7 +3,7 @@ .issue_title { @extend .top_box_content; .clearfix { - margin-bottom:0px; + margin-bottom: 0px; input { @extend .span8; } @@ -11,14 +11,14 @@ } .issue_middle_block { @extend .middle_box_content; - height:30px; + height: 30px; .issue_assignee { @extend .span6; - float:left; + float: left; } .issue_milestone { @extend .span4; - float:left; + float: left; } } .issue_description { @@ -28,31 +28,31 @@ .issues_table { .issue { - padding:7px 10px; + padding: 7px 10px; .issue_check { - float:left; + float: left; padding: 8px 0; padding-right: 8px; min-width: 15px; } p { - padding-top:0; - padding-bottom:2px; + padding-top: 0; + padding-bottom: 2px; } img.avatar { - width:32px; - margin-top:1px; + width: 32px; + margin-top: 1px; } } } input.check_all_issues { - float:left; + float: left; padding: 0; - margin:0; + margin: 0; margin-right: 10px; position: relative; top: 8px; @@ -82,16 +82,16 @@ input.check_all_issues { } } -@media (min-width: 800px) { .issues_filters select { width:160px; } } -@media (min-width: 1000px) { .issues_filters select { width:200px; } } -@media (min-width: 1200px) { .issues_filters select { width:220px; } } +@media (min-width: 800px) { .issues_filters select { width: 160px; } } +@media (min-width: 1000px) { .issues_filters select { width: 200px; } } +@media (min-width: 1200px) { .issues_filters select { width: 220px; } } #issues-table-holder { .issues_filters { form { - padding:0; - margin:0; + padding: 0; + margin: 0; margin-top:7px } } @@ -99,27 +99,27 @@ input.check_all_issues { .issues_bulk_update { margin: 0; form { - padding:0; - margin:0; + padding: 0; + margin: 0; margin-top:7px } .update_selected_issues { - position:relative; + position: relative; top:-2px; - margin-left:4px; - float:left; + margin-left: 4px; + float: left; } .update_issues_text { - padding:3px; + padding: 3px; line-height: 18px; - float:left; + float: left; } } } #update_status { - width:100px; + width: 100px; } @@ -136,11 +136,11 @@ input.check_all_issues { */ .ui-datepicker { - border:none; - box-shadow:none; + border: none; + box-shadow: none; .ui-datepicker-header { - @include solid_shade; - margin-bottom:10px; - border:1px solid #bbb; + @include solid-shade; + margin-bottom: 10px; + border: 1px solid #bbb; } } diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss index 5b8763cfec0..8c21de70013 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/sections/login.scss @@ -1,13 +1,13 @@ /* Login Page */ body.login-page{ padding-top: 10%; - background:#f1f1f1; + background: #f1f1f1; } .login-box{ width: 304px; position: relative; - border-radius: 5px; + @include border-radius(5px); margin: auto; padding: 20px; background: white; @@ -18,25 +18,15 @@ body.login-page{ display: block; } -.login-box input.text{background-color: #f1f1f1; font-size: 16px; border-radius: 0; padding: 14px 10px; width: 280px} +.login-box input.text{background-color: #f1f1f1; font-size: 16px; @include border-radius(0); padding: 14px 10px; width: 280px} .login-box input.text.top{ - -webkit-border-top-left-radius: 5px; - -webkit-border-top-right-radius: 5px; - -moz-border-radius-topleft: 5px; - -moz-border-radius-topright: 5px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - margin-bottom:0px; + @include border-radius(5px 5px 0 0); + margin-bottom: 0px; } .login-box input.text.bottom{ - -webkit-border-bottom-right-radius: 5px; - -webkit-border-bottom-left-radius: 5px; - -moz-border-radius-bottomright: 5px; - -moz-border-radius-bottomleft: 5px; - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; + @include border-radius(0 0 5px 5px); border-top: 0; margin-bottom: 20px; } diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index 9087e7c2f59..a5ec1756e5f 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -5,10 +5,10 @@ .mr_branch_box { @extend .ui-box; - margin-bottom:20px; + margin-bottom: 20px; .body { - background:#f1f1f1; + background: #f1f1f1; } } @@ -23,31 +23,30 @@ } form { - margin-bottom:0; + margin-bottom: 0; .clearfix { - margin-bottom:0; + margin-bottom: 0; } } .accept_group { - float:left; + float: left; border: 1px solid #ADA; padding: 2px; @include border-radius(5px); - border-radius: 5px; background: #CEB; .accept_merge_request { - font-size:13px; - float:left; + font-size: 13px; + float: left; } .remove_branch_holder { - margin-left:20px; - margin-right:10px; - float:left; + margin-left: 20px; + margin-right: 10px; + float: left; } label { - color:#444; + color: #444; } } @@ -60,15 +59,15 @@ .mr_nav_tabs { li { a { - font-weight:bold; - padding:8px 20px; - text-align:center; + font-weight: bold; + padding: 8px 20px; + text-align: center; } } } li.merge_request { - padding:7px 10px; + padding: 7px 10px; img.avatar { width: 32px; margin-top: 1px; @@ -85,35 +84,35 @@ li.merge_request { } .label_branch { - @include round-borders-all(4px); - padding:2px 4px; - border:none; - font-size:14px; + @include border-radius(4px); + padding: 2px 4px; + border: none; + font-size: 14px; background: #474D57; - color:#fff; + color: #fff; font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; } .mr_source_commit, .mr_target_commit { .commit { - margin:0; - padding:0; + margin: 0; + padding: 0; padding: 5px; margin-bottom: 5px; .avatar { position:relative } .row_title { - color:#444; + color: #444; } .commit-author-name, .dash, .committed_ago, .browse_code_link_holder { - display:none; + display: none; } - list-style:none; + list-style: none; &:hover { - background:none; + background: none; } } } @@ -126,14 +125,14 @@ li.merge_request { @extend .main_box; .merge_requests_middle_box { @extend .middle_box_content; - height:30px; + height: 30px; .merge_requests_assignee { @extend .span6; - float:left; + float: left; } .merge_requests_milestone { @extend .span4; - float:left; + float: left; } } } diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 5707216922c..595568fc059 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -3,36 +3,35 @@ * */ ul.main_menu { - border-radius: 4px; + @include border-radius(4px); margin: auto; - margin:30px 0; - border:1px solid #BBB; - height:37px; + margin: 30px 0; + border: 1px solid #BBB; + height: 37px; @include bg-gray-gradient; - position:relative; - overflow:hidden; + position: relative; + overflow: hidden; @include shade; .count { position: relative; - top: -1px; - display: inline-block; - height: 15px; - margin: 0 0 0 5px; - padding: 0 8px 1px 8px; - height: auto; - font-size: 0.82em; - line-height: 14px; - text-align: center; - color: #777; - background: #f2f2f2; - border-top: 1px solid #CCC; - border-radius: 8px; - -moz-border-radius: 8px; + top: -1px; + display: inline-block; + height: 15px; + margin: 0 0 0 5px; + padding: 0 8px 1px 8px; + height: auto; + font-size: 0.82em; + line-height: 14px; + text-align: center; + color: #777; + background: #f2f2f2; + border-top: 1px solid #CCC; + @include border-radius(8px); } .label { - background:$hover; - text-shadow:none; - color:$style_color; + background: $hover; + text-shadow: none; + color: $style_color; } li { list-style-type: none; @@ -41,26 +40,21 @@ ul.main_menu { width: 1%; border-right: 1px solid #DDD; border-left: 1px solid #EEE; - border-bottom:2px solid #CFCFCF; + border-bottom: 2px solid #CFCFCF; &:first-child{ - -webkit-border-top-left-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - -moz-border-radius-topleft: 4px; - -moz-border-radius-bottomleft: 4px; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; + @include border-radius(5px 0 0 5px); border-left: 0; } &.active { - background-color:#D5D5D5; + background-color: #D5D5D5; border-right: 1px solid #BBB; border-left: 1px solid #BBB; - border-radius: 0 0 1px 1px; + @include border-radius(0 0 1px 1px); &:first-child{ - border-bottom:none; - border-left:none; + border-bottom: none; + border-left: none; } } @@ -68,10 +62,10 @@ ul.main_menu { a { background: url(home_icon.PNG) no-repeat center center; text-indent:-9999px; - min-width:20px; + min-width: 20px; img { - position:relative; - top:4px; + position: relative; + top: 4px; } } } @@ -79,12 +73,12 @@ ul.main_menu { a { display: block; text-align: center; - font-weight:bold; - height:35px; - line-height:36px; + font-weight: bold; + height: 35px; + line-height: 36px; color: $style_color; - text-shadow:0 1px 1px white; - padding:0 10px; + text-shadow: 0 1px 1px white; + padding: 0 10px; } } /* diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index d24d070df1e..0c2a56d62f5 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -4,30 +4,30 @@ */ #notes-list, #new-notes-list { - display:block; - list-style:none; - margin:0px; - padding:0px; + display: block; + list-style: none; + margin: 0px; + padding: 0px; } .issue_notes, .wiki_notes { .note_content { - float:left; - width:400px; + float: left; + width: 400px; } } /* Note textare */ #note_note { - height:80px; - width:99%; - font-size:14px; + height: 80px; + width: 99%; + font-size: 14px; } #new_note { .attach_holder { - display:none; + display: none; } } @@ -36,34 +36,34 @@ border: 1px solid #ddd; padding: 10px; min-height: 60px; - background:#f5f5f5; + background: #f5f5f5; } .note { padding: 8px 0; overflow: hidden; display: block; - position:relative; + position: relative; img {float: left; margin-right: 10px;} - img.emoji {float:none;margin:0;} + img.emoji {float: none;margin: 0;} .note-author cite{font-style: italic;} - p { color:$style_color; } + p { color: $style_color; } .note-author { color: $style_color;} - .note-title { margin-left:45px; padding-top: 5px;} + .note-title { margin-left: 45px; padding-top: 5px;} .avatar { - margin-top:3px; + margin-top: 3px; } .delete-note { - display:none; - position:absolute; - right:0; - top:0; + display: none; + position: absolute; + right: 0; + top: 0; } &:hover { - .delete-note { display:block; } + .delete-note { display: block; } } } #notes-list:not(.reversed) .note, @@ -94,30 +94,31 @@ p.notify_controls span{ } tr.line_notes_row { - border-bottom:1px solid #DDD; + border-bottom: 1px solid #DDD; border-left: 7px solid #2A79A3; &.reply { - background:#eee; + background: #eee; border-left: 7px solid #2A79A3; - border-top:1px solid #ddd; + border-top: 1px solid #ddd; td { - padding:7px 10px; + padding: 7px 10px; } a.line_note_reply_link { - @include round-borders-all(4px); + border: 1px solid #eaeaea; + @include border-radius(4px); padding: 3px 10px; - margin-left:5px; + margin-left: 5px; color: white; background: #2A79A3; border-color: #2A79A3; } } ul { - margin:0; + margin: 0; li { - padding:0; - border:none; + padding: 0; + border: none; } } } @@ -125,28 +126,28 @@ tr.line_notes_row { .line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } .per_line_form { - background:#f5f5f5; - border-top:1px solid #eee; + background: #f5f5f5; + border-top: 1px solid #eee; form { margin: 0; } td { - border-bottom:1px solid #ddd; + border-bottom: 1px solid #ddd; } .note_actions { - margin:0; + margin: 0; padding-top: 10px; .buttons { - float:left; - width:300px; + float: left; + width: 300px; } .options { .labels { - float:left; - padding-left:10px; + float: left; + padding-left: 10px; label { padding: 6px 0; margin: 0; - width:120px; + width: 120px; } } } @@ -154,13 +155,13 @@ tr.line_notes_row { } td .line_note_link { - position:absolute; + position: absolute; margin-left:-70px; margin-top:-10px; - z-index:10; + z-index: 10; background: url("comment_add.png") no-repeat left 0; - width:32px; - height:32px; + width: 32px; + height: 32px; opacity: 0.0; filter: alpha(opacity=0); @@ -180,13 +181,13 @@ td .line_note_link { .new_note { .input-file { font: 500px monospace; - opacity:0; + opacity: 0; filter: alpha(opacity=0); position: absolute; z-index: 1; - top:0; - right:0; - padding:0; + top: 0; + right: 0; + padding: 0; margin: 0; } @@ -198,24 +199,24 @@ td .line_note_link { } .attachments { - position:relative; + position: relative; width: 350px; height: 50px; - overflow:hidden; + overflow: hidden; margin:0 0 5px !important; .input_file { .file_upload { position: absolute; - right:14px; - top:7px; + right: 14px; + top: 7px; } .file_name { - line-height:30px; - width:240px; - height:28px; - overflow:hidden; + line-height: 30px; + width: 240px; + height: 28px; + overflow: hidden; } .input-file { width: 260px; @@ -228,5 +229,5 @@ td .line_note_link { .note-text { border: 1px solid #aaa; - box-shadow:none; + box-shadow: none; } diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index e945ca00918..607daf7a97e 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -1,21 +1,21 @@ .profile_history { .event_feed { - min-height:20px; + min-height: 20px; .avatar { - width:20px; + width: 20px; } } } .profile_avatar_holder { - float:left; - width:60px; - height:60px; - margin-right:20px; + float: left; + width: 60px; + height: 60px; + margin-right: 20px; img { - width:60px; - height:60px; - background:#fff; + width: 60px; + height: 60px; + background: #fff; padding: 1px; border: 1px solid #ddd; } diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index c9d386ab5bb..452fbc2bdfb 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -10,38 +10,38 @@ .groups_box, .projects_box { h5 { - color:$style_color; - font-size:16px; + color: $style_color; + font-size: 16px; text-shadow: 0 1px 1px #fff; padding: 2px 10px; - line-height:32px; - font-size:14px; + line-height: 32px; + font-size: 14px; } ul { li { - padding:0; + padding: 0; a { - display:block; + display: block; .group_name { - font-size:14px; - line-height:18px; + font-size: 14px; + line-height: 18px; } .project_name { - color:#4fa2bd; - font-size:14px; - line-height:18px; + color: #4fa2bd; + font-size: 14px; + line-height: 18px; } .arrow { - float:right; - padding:10px; - margin:0; + float: right; + padding: 10px; + margin: 0; } .last_activity { - padding-top:5px; - display:block; + padding-top: 5px; + display: block; span, strong { - font-size:12px; - color:#666; + font-size: 12px; + color: #666; } } } @@ -58,21 +58,21 @@ .project_name_holder { input, label { - font-size:16px; - line-height:20px; - padding:8px; + font-size: 16px; + line-height: 20px; + padding: 8px; } label { - color:#888; + color: #888; } .btn { - padding:6px 10px; - margin-left:10px; - margin-bottom:8px; + padding: 6px 10px; + margin-left: 10px; + margin-bottom: 8px; } } .adv_settings { - h6 { margin-left:40px; } + h6 { margin-left: 40px; } } } @@ -81,19 +81,20 @@ @include bg-gray-gradient; padding: 4px 7px; border: 1px solid #CCC; - margin-bottom:20px; + margin-bottom: 20px; } .project_clone_holder { input[type="text"], .btn { - font-size:12px; + font-size: 12px; line-height: 18px; margin: 0; padding: 3px 10px; } input[type="text"] { + @extend .monospace; border: 1px solid #BBB; box-shadow: none; margin-left: -1px; @@ -102,8 +103,8 @@ .save-project-loader { img { - margin-top:50px; - margin-bottom:50px; + margin-top: 50px; + margin-bottom: 50px; } h3 { @extend .page_title; diff --git a/app/assets/stylesheets/sections/themes.scss b/app/assets/stylesheets/sections/themes.scss index 2d121519b02..4e5eaf575ae 100644 --- a/app/assets/stylesheets/sections/themes.scss +++ b/app/assets/stylesheets/sections/themes.scss @@ -6,17 +6,17 @@ } .themes_opts { - padding-left:20px; + padding-left: 20px; label { - width:175px; - margin-right:40px; + width: 175px; + margin-right: 40px; .prev { @extend .thumbnail; - height:30px; - width:175px; - margin-bottom:10px; + height: 30px; + width: 175px; + margin-bottom: 10px; &.classic { background: #31363e; @@ -42,17 +42,17 @@ } .code_highlight_opts { - padding-left:20px; + padding-left: 20px; label { - width:220px; - margin-right:40px; + width: 220px; + margin-right: 40px; .prev { @extend .thumbnail; - height:151px; - width:220px; - margin-bottom:10px; + height: 151px; + width: 220px; + margin-bottom: 10px; } } } diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index f6bdb0f3dba..b0d795f4d5a 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -1,14 +1,14 @@ .tree-holder { .tree-content-holder { - float:left; - width:100%; + float: left; + width: 100%; } .tree_progress { - display:none; - margin:20px; + display: none; + margin: 20px; &.loading { - display:block; + display: block; } } @@ -18,20 +18,20 @@ &:hover { td { background: $hover; - border-top:1px solid #ADF; - border-bottom:1px solid #ADF; + border-top: 1px solid #ADF; + border-bottom: 1px solid #ADF; } - cursor:pointer; + cursor: pointer; } } } .tree-item { .tree-item-file-name { - vertical-align:middle; + vertical-align: middle; a { &:hover { - color:$blue_link; + color: $primary_color; } } @@ -48,8 +48,8 @@ padding: 2px 10px; } td { - line-height:20px; - background:#fafafa; + line-height: 20px; + background: #fafafa; } } @@ -86,7 +86,7 @@ .tree-btn-group { .btn { margin-right:-3px; - padding:2px 10px; + padding: 2px 10px; } } diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss index 1f3d3d3d389..fee179899ce 100644 --- a/app/assets/stylesheets/themes/ui_basic.scss +++ b/app/assets/stylesheets/themes/ui_basic.scss @@ -11,8 +11,8 @@ a { color: $link_color; &:hover { - text-decoration:none; - color: $blue_link; + text-decoration: none; + color: $primary_color; } } diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index a9d2124130d..9e6433c5f9e 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -47,17 +47,17 @@ a { h1 { background: url('logo_white.png') no-repeat 0px 2px; - color:#eee; + color: #eee; text-shadow: 0 1px 1px #111; } } .separator { - display:none; + display: none; } } .project_name { - color:#eee; + color: #eee; text-shadow: 0 1px 1px #111; } } diff --git a/app/assets/stylesheets/variables.scss b/app/assets/stylesheets/variables.scss new file mode 100644 index 00000000000..ba78c8351f4 --- /dev/null +++ b/app/assets/stylesheets/variables.scss @@ -0,0 +1,5 @@ +/** Colors **/ +$primary_color: #2FA0BB; +$link_color: #3A89A3; +$style_color: #474D57; +$hover: #D9EDF7;
\ No newline at end of file diff --git a/app/contexts/notes/load_context.rb b/app/contexts/notes/load_context.rb index f3949149a06..9f8299f52f7 100644 --- a/app/contexts/notes/load_context.rb +++ b/app/contexts/notes/load_context.rb @@ -19,8 +19,6 @@ module Notes when "wall" # this is the only case, where the order is DESC project.common_notes.order("created_at DESC, id DESC").limit(50) - when "wiki" - project.wiki_notes.limit(20) end @notes = if after_id diff --git a/app/contexts/project_update_context.rb b/app/contexts/project_update_context.rb new file mode 100644 index 00000000000..e28d43d0e81 --- /dev/null +++ b/app/contexts/project_update_context.rb @@ -0,0 +1,21 @@ +class ProjectUpdateContext < BaseContext + def execute(role = :default) + namespace_id = params[:project].delete(:namespace_id) + + if namespace_id.present? + if namespace_id == Namespace.global_id + if project.namespace.present? + # Transfer to global namespace from anyone + project.transfer(nil) + end + elsif namespace_id.to_i != project.namespace_id + # Transfer to someone namespace + namespace = Namespace.find(namespace_id) + project.transfer(namespace) + end + end + + project.update_attributes(params[:project], as: role) + end +end + diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 0bba019918f..8a0a9e9b245 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -22,6 +22,7 @@ class Admin::GroupsController < AdminController def create @group = Group.new(params[:group]) + @group.path = @group.name.dup.parameterize if @group.name @group.owner = current_user if @group.save @@ -48,15 +49,17 @@ class Admin::GroupsController < AdminController def project_update project_ids = params[:project_ids] - Project.where(id: project_ids).update_all(group_id: @group.id) + + Project.where(id: project_ids).each do |project| + project.transfer(@group) + end redirect_to :back, notice: 'Group was successfully updated.' end def remove_project @project = Project.find(params[:project_id]) - @project.group_id = nil - @project.save + @project.transfer(nil) redirect_to :back, notice: 'Group was successfully updated.' end @@ -70,6 +73,6 @@ class Admin::GroupsController < AdminController private def group - @group = Group.find_by_code(params[:id]) + @group = Group.find_by_path(params[:id]) end end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index d27b657de3a..e61f94f8cf3 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -1,65 +1,50 @@ class Admin::ProjectsController < AdminController - before_filter :admin_project, only: [:edit, :show, :update, :destroy, :team_update] + before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] def index - @admin_projects = Project.scoped - @admin_projects = @admin_projects.search(params[:name]) if params[:name].present? - @admin_projects = @admin_projects.order("name ASC").page(params[:page]).per(20) + @projects = Project.scoped + @projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present? + @projects = @projects.search(params[:name]) if params[:name].present? + @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) end def show @users = User.scoped - @users = @users.not_in_project(@admin_project) if @admin_project.users.present? + @users = @users.not_in_project(@project) if @project.users.present? @users = @users.all end - def new - @admin_project = Project.new - end - def edit end def team_update - @admin_project.add_users_ids_to_team(params[:user_ids], params[:project_access]) - - redirect_to [:admin, @admin_project], notice: 'Project was successfully updated.' - end - - def create - @admin_project = Project.new(params[:project]) - @admin_project.owner = current_user + @project.add_users_ids_to_team(params[:user_ids], params[:project_access]) - if @admin_project.save - redirect_to [:admin, @admin_project], notice: 'Project was successfully created.' - else - render action: "new" - end + redirect_to [:admin, @project], notice: 'Project was successfully updated.' end def update - owner_id = params[:project].delete(:owner_id) + status = ProjectUpdateContext.new(project, current_user, params).execute(:admin) - if owner_id - @admin_project.owner = User.find(owner_id) - end - - if @admin_project.update_attributes(params[:project]) - redirect_to [:admin, @admin_project], notice: 'Project was successfully updated.' + if status + redirect_to [:admin, @project], notice: 'Project was successfully updated.' else render action: "edit" end end def destroy - @admin_project.destroy + @project.destroy - redirect_to admin_projects_url, notice: 'Project was successfully deleted.' + redirect_to admin_projects_path, notice: 'Project was successfully deleted.' end - private + protected + + def project + id = params[:project_id] || params[:id] - def admin_project - @admin_project = Project.find_by_code(params[:id]) + @project = Project.find_with_namespace(id) + @project || render_404 end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4b3623aec0f..5735c1d2916 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,7 @@ class ApplicationController < ActionController::Base before_filter :authenticate_user! before_filter :reject_blocked! before_filter :set_current_user_for_observers + before_filter :add_abilities before_filter :dev_tools if Rails.env == 'development' protect_from_forgery @@ -63,11 +64,19 @@ class ApplicationController < ActionController::Base end def project - @project ||= current_user.projects.find_by_code(params[:project_id] || params[:id]) - @project || render_404 + id = params[:project_id] || params[:id] + + @project = Project.find_with_namespace(id) + + if @project and can?(current_user, :read_project, @project) + @project + else + @project = nil + render_404 + end end - def add_project_abilities + def add_abilities abilities << Ability end diff --git a/app/controllers/commit_controller.rb b/app/controllers/commit_controller.rb index 97998352255..d671e9f9e9e 100644 --- a/app/controllers/commit_controller.rb +++ b/app/controllers/commit_controller.rb @@ -26,7 +26,8 @@ class CommitController < ProjectResourceController end end - format.patch + format.diff { render text: @commit.to_diff } + format.patch { render text: @commit.to_patch } end end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 8d9329f2b32..461dd51b570 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,11 +1,21 @@ class DashboardController < ApplicationController respond_to :html + before_filter :projects before_filter :event_filter, only: :index def index - @groups = Group.where(id: current_user.projects.pluck(:group_id)) - @projects = current_user.projects_sorted_by_activity + @groups = current_user.authorized_groups + + @projects = case params[:scope] + when 'personal' then + @projects.personal(current_user) + when 'joined' then + @projects.joined(current_user) + else + @projects + end + @projects = @projects.page(params[:page]).per(30) @events = Event.in_projects(current_user.project_ids) @@ -23,15 +33,16 @@ class DashboardController < ApplicationController # Get authored or assigned open merge requests def merge_requests - @projects = current_user.projects.all - @merge_requests = current_user.cared_merge_requests.recent.page(params[:page]).per(20) + @merge_requests = current_user.cared_merge_requests + @merge_requests = dashboard_filter(@merge_requests) + @merge_requests = @merge_requests.recent.page(params[:page]).per(20) end # Get only assigned issues def issues - @projects = current_user.projects.all - @user = current_user - @issues = current_user.assigned_issues.opened.recent.page(params[:page]).per(20) + @issues = current_user.assigned_issues + @issues = dashboard_filter(@issues) + @issues = @issues.recent.page(params[:page]).per(20) @issues = @issues.includes(:author, :project) respond_to do |format| @@ -40,7 +51,32 @@ class DashboardController < ApplicationController end end + protected + + def projects + @projects = current_user.authorized_projects.sorted_by_activity + end + def event_filter @event_filter ||= EventFilter.new(params[:event_filter]) end + + def dashboard_filter items + if params[:project_id] + items = items.where(project_id: params[:project_id]) + end + + if params[:search].present? + items = items.search(params[:search]) + end + + case params[:status] + when 'closed' + items.closed + when 'all' + items + else + items.opened + end + end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 63f70cd0027..93c495363d9 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -5,6 +5,9 @@ class GroupsController < ApplicationController before_filter :group before_filter :projects + # Authorize + before_filter :authorize_read_group! + def show @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) @last_push = current_user.recent_push @@ -44,20 +47,32 @@ class GroupsController < ApplicationController end def people - @users = group.users.all + @project = group.projects.find(params[:project_id]) if params[:project_id] + @users = @project ? @project.users : group.users + + if @project + @team_member = @project.users_projects.new + end end protected def group - @group ||= Group.find_by_code(params[:id]) + @group ||= Group.find_by_path(params[:id]) end def projects - @projects ||= current_user.projects_sorted_by_activity.where(group_id: @group.id) + @projects ||= group.projects.authorized_for(current_user).sorted_by_activity end def project_ids projects.map(&:id) end + + # Dont allow unauthorized access to group + def authorize_read_group! + unless projects.present? or can?(current_user, :manage_group, @group) + return render_404 + end + end end diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index 8e180c9baae..362962707fd 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/app/controllers/merge_requests_controller.rb @@ -1,7 +1,7 @@ class MergeRequestsController < ProjectResourceController before_filter :module_enabled - before_filter :merge_request, only: [:edit, :update, :destroy, :show, :commits, :diffs, :automerge, :automerge_check, :raw] - before_filter :validates_merge_request, only: [:show, :diffs, :raw] + before_filter :merge_request, only: [:edit, :update, :destroy, :show, :commits, :diffs, :automerge, :automerge_check] + before_filter :validates_merge_request, only: [:show, :diffs] before_filter :define_show_vars, only: [:show, :diffs] # Allow read any merge_request @@ -16,7 +16,6 @@ class MergeRequestsController < ProjectResourceController # Allow destroy merge_request before_filter :authorize_admin_merge_request!, only: [:destroy] - def index @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute end @@ -25,11 +24,10 @@ class MergeRequestsController < ProjectResourceController respond_to do |format| format.html format.js - end - end - def raw - send_file @merge_request.to_raw + format.diff { render text: @merge_request.to_diff } + format.patch { render text: @merge_request.to_patch } + end end def diffs diff --git a/app/controllers/project_resource_controller.rb b/app/controllers/project_resource_controller.rb index d297bba635f..81bc3a91bd1 100644 --- a/app/controllers/project_resource_controller.rb +++ b/app/controllers/project_resource_controller.rb @@ -1,5 +1,3 @@ class ProjectResourceController < ApplicationController before_filter :project - # Authorize - before_filter :add_project_abilities end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 72080070bed..a6e7f1f93fb 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -34,8 +34,11 @@ class ProjectsController < ProjectResourceController end def update + status = ProjectUpdateContext.new(project, current_user, params).execute + respond_to do |format| - if project.update_attributes(params[:project]) + if status + flash[:notice] = 'Project was successfully updated.' format.html { redirect_to edit_project_path(project), notice: 'Project was successfully updated.' } format.js else diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb index 37ed74b231f..311af62b8db 100644 --- a/app/controllers/team_members_controller.rb +++ b/app/controllers/team_members_controller.rb @@ -21,7 +21,11 @@ class TeamMembersController < ProjectResourceController params[:project_access] ) - redirect_to project_team_index_path(@project) + if params[:redirect_to] + redirect_to params[:redirect_to] + else + redirect_to project_team_index_path(@project) + end end def update diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cba34c963a0..a689213bcee 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -75,7 +75,7 @@ module ApplicationHelper end def search_autocomplete_source - projects = current_user.projects.map{ |p| { label: p.name, url: project_path(p) } } + projects = current_user.projects.map{ |p| { label: p.name_with_namespace, url: project_path(p) } } default_nav = [ { label: "My Profile", url: profile_path }, @@ -126,6 +126,10 @@ module ApplicationHelper Gitlab::Theme.css_class_by_id(current_user.try(:theme_id)) end + def user_color_scheme_class + current_user.dark_scheme ? :black : :white + end + def show_last_push_widget?(event) event && event.last_push_to_non_root? && diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb new file mode 100644 index 00000000000..0baa5b4108e --- /dev/null +++ b/app/helpers/dashboard_helper.rb @@ -0,0 +1,32 @@ +module DashboardHelper + def dashboard_filter_path(entity, options={}) + exist_opts = { + status: params[:status], + project_id: params[:project_id], + } + + options = exist_opts.merge(options) + + case entity + when 'issue' then + dashboard_issues_path(options) + when 'merge_request' + dashboard_merge_requests_path(options) + end + end + + def entities_per_project project, entity + items = project.items_for(entity) + + items = case params[:status] + when 'closed' + items.closed + when 'all' + items + else + items.opened + end + + items.where(assignee_id: current_user.id).count + end +end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb new file mode 100644 index 00000000000..fdf6725cc13 --- /dev/null +++ b/app/helpers/namespaces_helper.rb @@ -0,0 +1,26 @@ +module NamespacesHelper + def namespaces_options(selected = :current_user, scope = :default) + groups = current_user.namespaces.select {|n| n.type == 'Group'} + + users = if scope == :all + Namespace.root + else + current_user.namespaces.reject {|n| n.type == 'Group'} + end + + global_opts = ["Global", [['/', Namespace.global_id]] ] + group_opts = ["Groups", groups.map {|g| [g.human_name, g.id]} ] + users_opts = [ "Users", users.map {|u| [u.human_name, u.id]} ] + + options = [] + options << global_opts if current_user.admin + options << group_opts + options << users_opts + + if selected == :current_user && current_user.namespace + selected = current_user.namespace.id + end + + grouped_options_for_select(options, selected) + end +end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 740700c3009..a4bec87caa7 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -84,4 +84,17 @@ module TabHelper 'active' end end + + # Use nav_tab for save controller/action but different params + def nav_tab key, value, &block + o = {} + o[:class] = "" + o[:class] << " active" if params[key] == value + + if block_given? + content_tag(:li, capture(&block), o) + else + content_tag(:li, nil, o) + end + end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 7f3862adb1d..29cebadaf94 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -89,14 +89,6 @@ class Notify < ActionMailer::Base mail(to: recipient(recipient_id), subject: subject) end - def note_wiki_email(recipient_id, note_id) - @note = Note.find(note_id) - @wiki = @note.noteable - @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for wiki")) - end - - # # Project @@ -105,7 +97,7 @@ class Notify < ActionMailer::Base def project_access_granted_email(user_project_id) @users_project = UsersProject.find user_project_id @project = @users_project.project - mail(to: @users_project.user.email, + mail(to: @users_project.user.email, subject: subject("access to project was granted")) end diff --git a/app/models/ability.rb b/app/models/ability.rb index c3a212f473d..b09899f17dc 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -7,6 +7,7 @@ class Ability when "Note" then note_abilities(object, subject) when "Snippet" then snippet_abilities(object, subject) when "MergeRequest" then merge_request_abilities(object, subject) + when "Group" then group_abilities(object, subject) else [] end end @@ -14,7 +15,42 @@ class Ability def project_abilities(user, project) rules = [] - rules << [ + # Rules based on role in project + if project.master_access_for?(user) + # TODO: replace with master rules. + # Only allow project administration for namespace owners + rules << project_admin_rules + + elsif project.dev_access_for?(user) + rules << project_dev_rules + + elsif project.report_access_for?(user) + rules << project_report_rules + + elsif project.guest_access_for?(user) + rules << project_guest_rules + end + + if project.namespace + # If user own project namespace + # (Ex. group owner or account owner) + if project.namespace.owner == user + rules << project_admin_rules + end + else + # For compatibility with global projects + # use projects.owner_id + if project.owner == user + rules << project_admin_rules + end + end + + + rules.flatten + end + + def project_guest_rules + [ :read_project, :read_wiki, :read_issue, @@ -26,28 +62,30 @@ class Ability :write_project, :write_issue, :write_note - ] if project.guest_access_for?(user) + ] + end - rules << [ + def project_report_rules + project_guest_rules + [ :download_code, :write_merge_request, :write_snippet - ] if project.report_access_for?(user) + ] + end - rules << [ + def project_dev_rules + project_report_rules + [ :write_wiki, :push_code - ] if project.dev_access_for?(user) - - rules << [ - :push_code_to_protected_branches - ] if project.master_access_for?(user) + ] + end - rules << [ + def project_master_rules + project_dev_rules + [ + :push_code_to_protected_branches, :modify_issue, :modify_snippet, :modify_merge_request, - :admin_project, :admin_issue, :admin_milestone, :admin_snippet, @@ -56,7 +94,21 @@ class Ability :admin_note, :accept_mr, :admin_wiki - ] if project.master_access_for?(user) || project.owner == user + ] + end + + def project_admin_rules + project_master_rules + [ + :admin_project + ] + end + + def group_abilities user, group + rules = [] + + rules << [ + :manage_group + ] if group.owner == user rules.flatten end diff --git a/app/models/commit.rb b/app/models/commit.rb index 5efb20cea3b..200c915a335 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -150,4 +150,19 @@ class Commit def parents_count parents && parents.count || 0 end + + # Shows the diff between the commit's parent and the commit. + # + # Cuts out the header and stats from #to_patch and returns only the diff. + def to_diff + # see Grit::Commit#show + patch = to_patch + + # discard lines before the diff + lines = patch.split("\n") + while !lines.first.start_with?("diff --git") do + lines.shift + end + lines.join("\n") + end end diff --git a/app/models/group.rb b/app/models/group.rb index 1ff6872f687..b668f5560ab 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,36 +1,24 @@ # == Schema Information # -# Table name: groups +# Table name: namespaces # # id :integer not null, primary key # name :string(255) not null -# code :string(255) not null +# path :string(255) not null # owner_id :integer not null # created_at :datetime not null # updated_at :datetime not null +# type :string(255) # -class Group < ActiveRecord::Base - attr_accessible :code, :name, :owner_id - - has_many :projects - belongs_to :owner, class_name: "User" - - validates :name, presence: true, uniqueness: true - validates :code, presence: true, uniqueness: true - validates :owner, presence: true - - delegate :name, to: :owner, allow_nil: true, prefix: true - - def self.search query - where("name LIKE :query OR code LIKE :query", query: "%#{query}%") - end - - def to_param - code +class Group < Namespace + def users + users = User.joins(:users_projects).where(users_projects: {project_id: project_ids}) + users = users << owner + users.uniq end - def users - User.joins(:users_projects).where(users_projects: {project_id: project_ids}).uniq + def human_name + name end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 0766e5baa72..8039813ad1c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -202,20 +202,22 @@ class MergeRequest < ActiveRecord::Base false end - def to_raw - FileUtils.mkdir_p(Rails.root.join("tmp", "patches")) - patch_path = Rails.root.join("tmp", "patches", "merge_request_#{self.id}.patch") - - from = commits.last.id - to = source_branch - - project.repo.git.run('', "format-patch" , " > #{patch_path.to_s}", {}, ["#{from}..#{to}", "--stdout"]) - - patch_path - end - def mr_and_commit_notes commit_ids = commits.map(&:id) Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND noteable_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids) end + + # Returns the raw diff for this merge request + # + # see "git diff" + def to_diff + project.repo.git.native(:diff, {timeout: 30, raise: true}, "#{target_branch}...#{source_branch}") + end + + # Returns the commit as a series of email patches. + # + # see "git format-patch" + def to_patch + project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}") + end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb new file mode 100644 index 00000000000..e1c24de949a --- /dev/null +++ b/app/models/namespace.rb @@ -0,0 +1,70 @@ +# == Schema Information +# +# Table name: namespaces +# +# id :integer not null, primary key +# name :string(255) not null +# path :string(255) not null +# owner_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) +# + +class Namespace < ActiveRecord::Base + attr_accessible :name, :path + + has_many :projects, dependent: :destroy + belongs_to :owner, class_name: "User" + + validates :name, presence: true, uniqueness: true + validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, + format: { with: Gitlab::Regex.path_regex, + message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } + validates :owner, presence: true + + delegate :name, to: :owner, allow_nil: true, prefix: true + + after_create :ensure_dir_exist + after_update :move_dir + after_destroy :rm_dir + + scope :root, where('type IS NULL') + + def self.search query + where("name LIKE :query OR path LIKE :query", query: "%#{query}%") + end + + def self.global_id + 'GLN' + end + + def to_param + path + end + + def human_name + owner_name + end + + def ensure_dir_exist + namespace_dir_path = File.join(Gitlab.config.git_base_path, path) + system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path) + end + + def move_dir + if path_changed? + old_path = File.join(Gitlab.config.git_base_path, path_was) + new_path = File.join(Gitlab.config.git_base_path, path) + if File.exists?(new_path) + raise "Already exists" + end + system("mv #{old_path} #{new_path}") + end + end + + def rm_dir + dir_path = File.join(Gitlab.config.git_base_path, path) + system("rm -rf #{dir_path}") + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 3cbc9417b8f..74d981f26d6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -9,14 +9,13 @@ # created_at :datetime not null # updated_at :datetime not null # private_flag :boolean default(TRUE), not null -# code :string(255) # owner_id :integer # default_branch :string(255) # issues_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null -# group_id :integer +# namespace_id :integer # require "grit" @@ -27,12 +26,16 @@ class Project < ActiveRecord::Base include Authority include Team - attr_accessible :name, :path, :description, :code, :default_branch, :issues_enabled, - :wall_enabled, :merge_requests_enabled, :wiki_enabled + attr_accessible :name, :path, :description, :default_branch, :issues_enabled, + :wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin] + + attr_accessible :namespace_id, :owner_id, as: :admin + attr_accessor :error_code # Relations - belongs_to :group + belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" + belongs_to :namespace belongs_to :owner, class_name: "User" has_many :users, through: :users_projects has_many :events, dependent: :destroy @@ -54,36 +57,79 @@ class Project < ActiveRecord::Base # Validations validates :owner, presence: true validates :description, length: { within: 0..2000 } - validates :name, uniqueness: true, presence: true, length: { within: 0..255 } - validates :path, uniqueness: true, presence: true, length: { within: 0..255 }, - format: { with: /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/, - message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } - validates :code, presence: true, uniqueness: true, length: { within: 1..255 }, - format: { with: /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/, + validates :name, presence: true, length: { within: 0..255 } + validates :path, presence: true, length: { within: 0..255 }, + format: { with: Gitlab::Regex.path_regex, message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } validates :issues_enabled, :wall_enabled, :merge_requests_enabled, :wiki_enabled, inclusion: { in: [true, false] } + + validates_uniqueness_of :name, scope: :namespace_id + validates_uniqueness_of :path, scope: :namespace_id + validate :check_limit, :repo_name # Scopes scope :public_only, where(private_flag: false) scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.projects.map(&:id) ) } scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } + scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } + scope :personal, ->(user) { where(namespace_id: user.namespace_id) } + scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } class << self + def authorized_for user + projects = includes(:users_projects, :namespace) + projects = projects.where("users_projects.user_id = :user_id or projects.owner_id = :user_id or namespaces.owner_id = :user_id", user_id: user.id) + end + def active joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC") end def search query - where("name LIKE :query OR code LIKE :query OR path LIKE :query", query: "%#{query}%") + where("projects.name LIKE :query OR projects.path LIKE :query", query: "%#{query}%") + end + + def find_with_namespace(id) + if id.include?("/") + id = id.split("/") + namespace_id = Namespace.find_by_path(id.first).id + where(namespace_id: namespace_id).find_by_path(id.last) + else + find_by_path(id) + end end def create_by_user(params, user) + namespace_id = params.delete(:namespace_id) + project = Project.new params Project.transaction do + + # Parametrize path for project + # + # Ex. + # 'GitLab HQ'.parameterize => "gitlab-hq" + # + project.path = project.name.dup.parameterize + project.owner = user + + # Apply namespace if user has access to it + # else fallback to user namespace + if namespace_id != Namespace.global_id + project.namespace_id = user.namespace_id + + if namespace_id + group = Group.find_by_id(namespace_id) + if user.can? :manage_group, group + project.namespace_id = namespace_id + end + end + end + project.save! # Add user as project master @@ -134,11 +180,15 @@ class Project < ActiveRecord::Base end def to_param - code + if namespace + namespace.path + "/" + path + else + path + end end def web_url - [Gitlab.config.url, code].join("/") + [Gitlab.config.url, path].join("/") end def common_notes @@ -173,10 +223,6 @@ class Project < ActiveRecord::Base last_event.try(:created_at) || updated_at end - def wiki_notes - Note.where(noteable_id: wikis.pluck(:id), noteable_type: 'Wiki', project_id: self.id) - end - def project_id self.id end @@ -192,4 +238,62 @@ class Project < ActiveRecord::Base def gitlab_ci? gitlab_ci_service && gitlab_ci_service.active end + + def path_with_namespace + if namespace + namespace.path + '/' + path + else + path + end + end + + # For compatibility with old code + def code + path + end + + def transfer(new_namespace) + Project.transaction do + old_namespace = namespace + self.namespace = new_namespace + + old_dir = old_namespace.try(:path) || '' + new_dir = new_namespace.try(:path) || '' + + old_repo = if old_dir.present? + File.join(old_dir, self.path) + else + self.path + end + + Gitlab::ProjectMover.new(self, old_dir, new_dir).execute + + git_host.move_repository(old_repo, self) + + save! + end + end + + def name_with_namespace + @name_with_namespace ||= begin + if namespace + namespace.human_name + " / " + name + else + name + end + end + end + + def items_for entity + case entity + when 'issue' then + issues + when 'merge_request' then + merge_requests + end + end + + def namespace_owner + namespace.try(:owner) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 6d539c1f498..3f2d7c92ea8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -30,6 +30,7 @@ # locked_at :datetime # extern_uid :string(255) # provider :string(255) +# username :string(255) # class User < ActiveRecord::Base @@ -38,13 +39,17 @@ class User < ActiveRecord::Base devise :database_authenticatable, :token_authenticatable, :lockable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable - attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, + attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password, - :extern_uid, :provider, :as => [:default, :admin] - attr_accessible :projects_limit, :as => :admin + :extern_uid, :provider, as: [:default, :admin] + attr_accessible :projects_limit, as: :admin attr_accessor :force_random_password + # Namespace for personal projects + has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy + has_many :groups, class_name: "Group", foreign_key: :owner_id + has_many :keys, dependent: :destroy has_many :projects, through: :users_projects has_many :users_projects, dependent: :destroy @@ -58,13 +63,19 @@ class User < ActiveRecord::Base has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy validates :bio, length: { within: 0..255 } - validates :extern_uid, :allow_blank => true, :uniqueness => {:scope => :provider} + validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} + validates :username, presence: true, uniqueness: true, + format: { with: Gitlab::Regex.username_regex, + message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } + before_validation :generate_password, on: :create before_save :ensure_authentication_token alias_attribute :private_token, :authentication_token + delegate :path, to: :namespace, allow_nil: true, prefix: true + # Scopes scope :not_in_project, ->(project) { where("id not in (:ids)", ids: project.users.map(&:id) ) } scope :admins, where(admin: true) @@ -112,4 +123,16 @@ class User < ActiveRecord::Base self.password = self.password_confirmation = Devise.friendly_token.first(8) end end + + def authorized_groups + @authorized_groups ||= begin + groups = Group.where(id: self.projects.pluck(:namespace_id)).all + groups = groups + self.groups + groups.uniq + end + end + + def authorized_projects + Project.authorized_for(self) + end end diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb index 62fd9bf8ac9..9f9762aea07 100644 --- a/app/observers/issue_observer.rb +++ b/app/observers/issue_observer.rb @@ -3,7 +3,7 @@ class IssueObserver < ActiveRecord::Observer def after_create(issue) if issue.assignee && issue.assignee != current_user - Notify.new_issue_email(issue.id).deliver + Notify.new_issue_email(issue.id).deliver end end @@ -14,8 +14,8 @@ class IssueObserver < ActiveRecord::Observer status = 'closed' if issue.is_being_closed? status = 'reopened' if issue.is_being_reopened? if status - Note.create_status_change_note(issue, current_user, status) - [issue.author, issue.assignee].compact.each do |recipient| + Note.create_status_change_note(issue, current_user, status) + [issue.author, issue.assignee].compact.each do |recipient| Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) end end diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb index 03a61709829..bd41e51e8e4 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -1,8 +1,11 @@ class ProjectObserver < ActiveRecord::Observer - def after_save(project) + def after_create(project) project.update_repository end + def after_save(project) + end + def after_destroy(project) log_info("Project \"#{project.name}\" was removed") diff --git a/app/observers/user_observer.rb b/app/observers/user_observer.rb index 654621f7e1c..09b3c1d622f 100644 --- a/app/observers/user_observer.rb +++ b/app/observers/user_observer.rb @@ -9,6 +9,16 @@ class UserObserver < ActiveRecord::Observer log_info("User \"#{user.name}\" (#{user.email}) was removed") end + def after_save user + if user.username_changed? + if user.namespace + user.namespace.update_attributes(path: user.username) + else + user.create_namespace!(path: user.username, name: user.name) + end + end + end + protected def log_info message diff --git a/app/roles/account.rb b/app/roles/account.rb index b80fbba0958..8157898fef1 100644 --- a/app/roles/account.rb +++ b/app/roles/account.rb @@ -26,6 +26,18 @@ module Account is_admin? end + def abilities + @abilities ||= begin + abilities = Six.new + abilities << Ability + abilities + end + end + + def can? action, subject + abilities.allowed?(self, action, subject) + end + def last_activity_project projects.first end @@ -68,6 +80,29 @@ module Account end def projects_sorted_by_activity - projects.order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") + projects.sorted_by_activity + end + + def namespaces + namespaces = [] + + # Add user account namespace + namespaces << self.namespace if self.namespace + + # Add groups you can manage + namespaces += if admin + Group.all + else + groups.all + end + namespaces + end + + def several_namespaces? + namespaces.size > 1 + end + + def namespace_id + namespace.try :id end end diff --git a/app/roles/push_observer.rb b/app/roles/push_observer.rb index 2ee60646e97..c5c5203d7a6 100644 --- a/app/roles/push_observer.rb +++ b/app/roles/push_observer.rb @@ -114,7 +114,7 @@ module PushObserver id: commit.id, message: commit.safe_message, timestamp: commit.date.xmlschema, - url: "#{Gitlab.config.url}/#{code}/commits/#{commit.id}", + url: "#{Gitlab.config.url}/#{path}/commits/#{commit.id}", author: { name: commit.author_name, email: commit.author_email diff --git a/app/roles/repository.rb b/app/roles/repository.rb index 88468117822..74cae5c8d5d 100644 --- a/app/roles/repository.rb +++ b/app/roles/repository.rb @@ -79,11 +79,15 @@ module Repository end def url_to_repo - git_host.url_to_repo(path) + git_host.url_to_repo(path_with_namespace) end def path_to_repo - File.join(Gitlab.config.git_base_path, "#{path}.git") + File.join(Gitlab.config.git_base_path, "#{path_with_namespace}.git") + end + + def namespace_dir + namespace.try(:path) || '' end def update_repository @@ -160,12 +164,12 @@ module Repository return nil unless commit # Build file path - file_name = self.code + "-" + commit.id.to_s + ".tar.gz" - storage_path = Rails.root.join("tmp", "repositories", self.code) + file_name = self.path + "-" + commit.id.to_s + ".tar.gz" + storage_path = Rails.root.join("tmp", "repositories", self.path_with_namespace) file_path = File.join(storage_path, file_name) # Put files into a directory before archiving - prefix = self.code + "/" + prefix = self.path + "/" # Create file if not exists unless File.exists?(file_path) @@ -181,7 +185,7 @@ module Repository end def http_url_to_repo - http_url = [Gitlab.config.url, "/", path, ".git"].join('') + http_url = [Gitlab.config.url, "/", path_with_namespace, ".git"].join('') end # Check if current branch name is marked as protected in the system diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index b0b59a46fdb..4320bda4999 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,5 +1,29 @@ .admin_dash.row - .span4 + .span3 + .ui-box + %h5 Projects + .data.padded + = link_to admin_projects_path do + %h1= Project.count + %hr + = link_to 'New Project', new_project_path, class: "btn small" + .span3 + .ui-box + %h5 Groups + .data.padded + = link_to admin_groups_path do + %h1= Group.count + %hr + = link_to 'New Group', new_admin_group_path, class: "btn small" + .span3 + .ui-box + %h5 Users + .data.padded + = link_to admin_users_path do + %h1= User.count + %hr + = link_to 'New User', new_admin_user_path, class: "btn small" + .span3 .ui-box %h5 Resque Workers @@ -19,32 +43,13 @@ %p %strong Resque status unknown - - .span4 - .ui-box - %h5 Projects - .data.padded - = link_to admin_projects_path do - %h1= Project.count - %hr - = link_to 'New Project', new_admin_project_path, class: "btn small" - .span4 - .ui-box - %h5 Users - .data.padded - = link_to admin_users_path do - %h1= User.count - %hr - = link_to 'New User', new_admin_user_path, class: "btn small" - - .row .span6 %h3 Latest projects %hr - @projects.each do |project| %p - = link_to project.name, [:admin, project] + = link_to project.name_with_namespace, [:admin, project] .span6 %h3 Latest users %hr diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index e85cce66ba1..67516eb26e3 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -7,13 +7,6 @@ Group name is .input = f.text_field :name, placeholder: "Example Group", class: "xxlarge" - .clearfix - = f.label :code do - URL - .input - .input-prepend - %span.add-on= web_app_url + 'groups/' - = f.text_field :code, placeholder: "example" .form-actions = f.submit 'Save group', class: "btn save-btn" diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 6a0794cfd44..952d515103c 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,4 +1,3 @@ -= render 'admin/shared/projects_head' %h3.page_title Groups %small @@ -14,7 +13,7 @@ %table %thead %th Name - %th Code + %th Path %th Projects %th Edit %th.cred Danger Zone! @@ -22,7 +21,7 @@ - @groups.each do |group| %tr %td= link_to group.name, [:admin, group] - %td= group.code + %td= group.path %td= group.projects.count %td= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small" %td.bgred= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger" diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml index d6b6ea1535e..6ff0e781d17 100644 --- a/app/views/admin/groups/new.html.haml +++ b/app/views/admin/groups/new.html.haml @@ -1,3 +1,21 @@ %h3.page_title New Group -%br -= render 'form' +%hr += form_for [:admin, @group] do |f| + - if @group.errors.any? + .alert-message.block-message.error + %span= @group.errors.full_messages.first + .clearfix + = f.label :name do + Group name is + .input + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" + + = f.submit 'Create group', class: "btn primary" + %hr + .padded + %ul + %li Group is kind of directory for several projects + %li All created groups are private + %li People within a group see only projects they have access to + %li All projects of group will be stored in group directory + %li You will be able to move existing projects into group diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 309a10e5bb4..d371acadd15 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -1,4 +1,3 @@ -= render 'admin/shared/projects_head' %h3.page_title Group: #{@group.name} = link_to edit_admin_group_path(@group), class: "btn right" do @@ -20,9 +19,9 @@ %tr %td %b - Code: + Path: %td - = @group.code + %span.monospace= File.join(Gitlab.config.git_base_path, @group.path) %tr %td %b @@ -43,10 +42,14 @@ = link_to 'Remove from group', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Are you sure?', method: :delete, class: "btn danger small" .clearfix -%br -%h3 Add new project -%br + = form_tag project_update_admin_group_path(@group), class: "bulk_import", method: :put do - = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' - .form-actions - = submit_tag 'Add', class: "btn primary" + %fieldset + %legend Move projects to group + .clearfix + = label_tag :project_ids do + Projects + .input + = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' + .form-actions + = submit_tag 'Add', class: "btn primary" diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 0efe6db7483..e33c5468555 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -3,12 +3,18 @@ = link_to "githost.log", "#githost", 'data-toggle' => 'tab' %li = link_to "application.log", "#application", 'data-toggle' => 'tab' + +%p.light To prevent perfomance issues admin logs output the last 2000 lines .tab-content .tab-pane.active#githost .file_holder#README .file_title %i.icon-file githost.log + .right + = link_to '#', class: 'log-bottom' do + %i.icon-arrow-down + Scroll down .file_content.logs %ol - Gitlab::GitLogger.read_latest.each do |line| @@ -19,6 +25,10 @@ .file_title %i.icon-file application.log + .right + = link_to '#', class: 'log-bottom' do + %i.icon-arrow-down + Scroll down .file_content.logs %ol - Gitlab::AppLogger.read_latest.each do |line| diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml index 4848e7391a3..e515c68c382 100644 --- a/app/views/admin/projects/_form.html.haml +++ b/app/views/admin/projects/_form.html.haml @@ -11,28 +11,18 @@ .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - %hr - .adv_settings - %h6 Advanced settings: + %fieldset.adv_settings + %legend Advanced settings: .clearfix = f.label :path do Path .input - .input-prepend - %strong - = text_field_tag :ppath, @admin_project.path_to_repo, class: "xlarge", disabled: true - .clearfix - = f.label :code do - URL - .input - .input-prepend - %span.add-on= web_app_url - = f.text_field :code, placeholder: "example" + = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true - unless project.new_record? .clearfix - = f.label :owner_id - .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'} + = f.label :namespace_id + .input= f.select :namespace_id, namespaces_options(@project.namespace_id), {}, {class: 'chosen'} - if project.repo_exists? .clearfix @@ -40,9 +30,8 @@ .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;") - unless project.new_record? - %hr - .adv_settings - %h6 Features: + %fieldset.adv_settings + %legend Features: .clearfix = f.label :issues_enabled, "Issues" diff --git a/app/views/admin/projects/_new_form.html.haml b/app/views/admin/projects/_new_form.html.haml deleted file mode 100644 index d793b6f3aff..00000000000 --- a/app/views/admin/projects/_new_form.html.haml +++ /dev/null @@ -1,29 +0,0 @@ -= form_for [:admin, @admin_project] do |f| - - if @admin_project.errors.any? - .alert-message.block-message.error - %span= @admin_project.errors.full_messages.first - .clearfix.project_name_holder - = f.label :name do - Project name is - .input - = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - = f.submit 'Create project', class: "btn primary project-submit" - - %hr - %div.adv_settings - %h6 Advanced settings: - .clearfix - = f.label :path do - Git Clone - .input - .input-prepend - %span.add-on= Gitlab.config.ssh_path - = f.text_field :path, placeholder: "example_project", disabled: !@admin_project.new_record? - %span.add-on= ".git" - .clearfix - = f.label :code do - URL - .input - .input-prepend - %span.add-on= web_app_url - = f.text_field :code, placeholder: "example" diff --git a/app/views/admin/projects/edit.html.haml b/app/views/admin/projects/edit.html.haml index b5255671154..7b59a0cc753 100644 --- a/app/views/admin/projects/edit.html.haml +++ b/app/views/admin/projects/edit.html.haml @@ -1,3 +1,3 @@ -%h3.page_title #{@admin_project.name} → Edit project +%h3.page_title #{@project.name} → Edit project %hr -= render 'form', project: @admin_project += render 'form', project: @project diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 3335fce0078..9bbcbc71111 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,9 +1,9 @@ -= render 'admin/shared/projects_head' %h3.page_title Projects - = link_to 'New Project', new_admin_project_path, class: "btn small right" + = link_to 'New Project', new_project_path, class: "btn small right" %br = form_tag admin_projects_path, method: :get, class: 'form-inline' do + = select_tag :namespace_id, namespaces_options(params[:namespace_id], :all), class: "chosen xlarge", prompt: "Project namespace" = text_field_tag :name, params[:name], class: "xlarge" = submit_tag "Search", class: "btn submit primary" @@ -16,12 +16,14 @@ %th Edit %th.cred Danger Zone! - - @admin_projects.each do |project| + - @projects.each do |project| %tr - %td= link_to project.name, [:admin, project] - %td= project.path + %td + = link_to project.name_with_namespace, [:admin, project] + %td + %span.monospace= project.path_with_namespace + ".git" %td= project.users_projects.count %td= last_commit(project) %td= link_to 'Edit', edit_admin_project_path(project), id: "edit_#{dom_id(project)}", class: "btn small" %td.bgred= link_to 'Destroy', [:admin, project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn small danger" -= paginate @admin_projects, theme: "admin" += paginate @projects, theme: "admin" diff --git a/app/views/admin/projects/new.html.haml b/app/views/admin/projects/new.html.haml deleted file mode 100644 index 933cb671142..00000000000 --- a/app/views/admin/projects/new.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -.project_new_holder - %h3.page_title - New Project - %hr - = render 'new_form' -%div.save-project-loader.hide - %center - = image_tag "ajax_loader.gif" - %h3 Creating project & repository. Please wait a few minutes - -:javascript - $(function(){ new Projects(); }); diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 78df8f2d2e9..47185308f41 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,11 +1,10 @@ -= render 'admin/shared/projects_head' %h3.page_title - Project: #{@admin_project.name} - = link_to edit_admin_project_path(@admin_project), class: "btn right" do + Project: #{@project.name_with_namespace} + = link_to edit_admin_project_path(@project), class: "btn right" do %i.icon-edit Edit -- if !@admin_project.has_post_receive_file? && @admin_project.has_commits? +- if !@project.has_post_receive_file? && @project.has_commits? %br .alert.alert-error %span @@ -25,36 +24,39 @@ %b Name: %td - = @admin_project.name + = @project.name %tr %td %b - Code: + Namespace: %td - = @admin_project.code + - if @project.namespace + = @project.namespace.human_name + - else + Global %tr %td %b Path: %td - = @admin_project.path + %code= @project.path_to_repo %tr %td %b - Owner: + Created by: %td - = @admin_project.owner_name || '(deleted)' + = @project.owner_name || '(deleted)' %tr %td %b Post Receive File: %td - = check_box_tag :post_receive_file, 1, @admin_project.has_post_receive_file?, disabled: true + = check_box_tag :post_receive_file, 1, @project.has_post_receive_file?, disabled: true %br %h3 Team %small - (#{@admin_project.users_projects.count}) + (#{@project.users_projects.count}) %br %table.zebra-striped %thead @@ -64,7 +66,7 @@ %th Repository Access %th - - @admin_project.users_projects.each do |tm| + - @project.users_projects.each do |tm| %tr %td = link_to tm.user_name, admin_user_path(tm.user) @@ -75,7 +77,7 @@ %br %h3 Add new team member %br -= form_tag team_update_admin_project_path(@admin_project), class: "bulk_import", method: :put do += form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do %table.zebra-striped %thead %tr diff --git a/app/views/admin/shared/_projects_head.html.haml b/app/views/admin/shared/_projects_head.html.haml deleted file mode 100644 index 3f5c34c533c..00000000000 --- a/app/views/admin/shared/_projects_head.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%ul.nav.nav-tabs - = nav_link(controller: :projects) do - = link_to 'Projects', admin_projects_path, class: "tab" - = nav_link(controller: :groups) do - = link_to 'Groups', admin_groups_path, class: "tab" diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 7010c2727c6..45195152cb7 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -6,47 +6,42 @@ - @admin_user.errors.full_messages.each do |msg| %li= msg - .row - .span7 - .ui-box - %br - .clearfix - = f.label :name - .input - = f.text_field :name - %span.help-inline * required - .clearfix - = f.label :email - .input - = f.text_field :email - %span.help-inline * required - %hr - -if f.object.new_record? - .clearfix - = f.label :force_random_password do - %span Generate random password - .input= f.check_box :force_random_password, {}, true, nil - - %div.password-fields - .clearfix - = f.label :password - .input= f.password_field :password, disabled: f.object.force_random_password - .clearfix - = f.label :password_confirmation - .input= f.password_field :password_confirmation, disabled: f.object.force_random_password - %hr - .clearfix - = f.label :skype - .input= f.text_field :skype - .clearfix - = f.label :linkedin - .input= f.text_field :linkedin - .clearfix - = f.label :twitter - .input= f.text_field :twitter - .span5 - .ui-box - %br + %fieldset + %legend Account + .clearfix + = f.label :name + .input + = f.text_field :name, required: true + %span.help-inline * required + .clearfix + = f.label :username + .input + = f.text_field :username, required: true + %span.help-inline * required + .clearfix + = f.label :email + .input + = f.text_field :email, required: true + %span.help-inline * required + + %fieldset + %legend Password + .clearfix + = f.label :password + .input= f.password_field :password, disabled: f.object.force_random_password + .clearfix + = f.label :password_confirmation + .input= f.password_field :password_confirmation, disabled: f.object.force_random_password + -if f.object.new_record? + .clearfix + = f.label :force_random_password do + %span Generate random password + .input= f.check_box :force_random_password, {}, true, nil + + %fieldset + %legend Access + .row + .span8 .clearfix = f.label :projects_limit .input= f.number_field :projects_limit @@ -55,23 +50,27 @@ = f.label :admin do %strong.cred Administrator .input= f.check_box :admin + .span4 - unless @admin_user.new_record? - %hr - .padded.cred + .alert.alert-error - if @admin_user.blocked - %span - This user is blocked and is not able to login to GitLab - .clearfix - = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn small right" + %p This user is blocked and is not able to login to GitLab + = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn small" - else - %span - Blocked users will be removed from all projects & will not be able to login to GitLab. - .clearfix - = link_to 'Block User', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small right danger" + %p Blocked users will be removed from all projects & will not be able to login to GitLab. + = link_to 'Block User', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" + %fieldset + %legend Profile + .clearfix + = f.label :skype + .input= f.text_field :skype + .clearfix + = f.label :linkedin + .input= f.text_field :linkedin + .clearfix + = f.label :twitter + .input= f.text_field :twitter - .row - .span6 - .span6 .actions = f.submit 'Save', class: "btn save-btn" - if @admin_user.new_record? diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml index 032e3cfaa99..f8ff77b8f53 100644 --- a/app/views/admin/users/edit.html.haml +++ b/app/views/admin/users/edit.html.haml @@ -1,3 +1,6 @@ -%h3.page_title #{@admin_user.name} → Edit user +%h3.page_title + #{@admin_user.name} → + %i.icon-edit + Edit user %hr = render 'form' diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 5ef94ef5f34..5d0f6fe1153 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -6,7 +6,7 @@ = form_tag admin_users_path, method: :get, class: 'form-inline' do = text_field_tag :name, params[:name], class: "xlarge" = submit_tag "Search", class: "btn submit primary" -%ul.nav.nav-pills +%ul.nav.nav-tabs %li{class: "#{'active' unless params[:filter]}"} = link_to "Active", admin_users_path %li{class: "#{'active' if params[:filter] == "admins"}"} @@ -23,24 +23,25 @@ %thead %th Admin %th Name + %th Username %th Email %th Projects %th Edit - %th Blocked %th.cred Danger Zone! - @admin_users.each do |user| %tr %td= check_box_tag "admin", 1, user.admin, disabled: :disabled %td= link_to user.name, [:admin, user] + %td= user.username %td= user.email %td= user.users_projects.count %td= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn small" - %td + %td.bgred - if user.blocked = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success" - else = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" - %td.bgred= link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger" + = link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger" = paginate @admin_users, theme: "admin" diff --git a/app/views/admin/users/new.html.haml b/app/views/admin/users/new.html.haml index 70ead0d3f7d..1e82b249cf1 100644 --- a/app/views/admin/users/new.html.haml +++ b/app/views/admin/users/new.html.haml @@ -1,3 +1,5 @@ -%h3.page_title New user -%br +%h3.page_title + %i.icon-plus + New user +%hr = render 'form' diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index e73f4d10876..6a42f787bab 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -24,6 +24,12 @@ %tr %td %b + Username: + %td + = @admin_user.username + %tr + %td + %b Admin: %td= check_box_tag "admin", 1, @admin_user.admin, disabled: :disabled %tr diff --git a/app/views/commit/show.patch.erb b/app/views/commit/show.patch.erb deleted file mode 100644 index ce1c3d023f5..00000000000 --- a/app/views/commit/show.patch.erb +++ /dev/null @@ -1 +0,0 @@ -<%= @commit.to_patch %> diff --git a/app/views/commits/_commit_box.html.haml b/app/views/commits/_commit_box.html.haml index 26753a1465f..8f7826e0c8d 100644 --- a/app/views/commits/_commit_box.html.haml +++ b/app/views/commits/_commit_box.html.haml @@ -5,9 +5,14 @@ %span.btn.disabled.grouped %i.icon-comment = @notes_count - = link_to project_commit_path(@project, @commit, format: :patch), class: "btn small grouped" do - %i.icon-download-alt - Get Patch + .left.btn-group + %a.btn.small.grouped.dropdown-toggle{ data: {toggle: :dropdown} } + %i.icon-download-alt + Download as + %span.caret + %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: "browse-button primary grouped" do %strong Browse Code » %h3.commit-title.page_title diff --git a/app/views/dashboard/_filter.html.haml b/app/views/dashboard/_filter.html.haml new file mode 100644 index 00000000000..4624af79948 --- /dev/null +++ b/app/views/dashboard/_filter.html.haml @@ -0,0 +1,33 @@ += form_tag dashboard_filter_path(entity), method: 'get' do + %fieldset.dashboard-search-filter + = search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' } + = button_tag type: 'submit', class: 'btn' do + %i.icon-search + + %fieldset + %legend Status: + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if !params[:status])} + = link_to dashboard_filter_path(entity, status: nil) do + Open + %li{class: ("active" if params[:status] == 'closed')} + = link_to dashboard_filter_path(entity, status: 'closed') do + Closed + %li{class: ("active" if params[:status] == 'all')} + = link_to dashboard_filter_path(entity, status: 'all') do + All + + %fieldset + %legend Projects: + %ul.nav.nav-pills.nav-stacked + - @projects.each do |project| + - unless entities_per_project(project, entity).zero? + %li{class: ("active" if params[:project_id] == project.id.to_s)} + = link_to dashboard_filter_path(entity, project_id: project.id) do + = project.name_with_namespace + %small.right= entities_per_project(project, entity) + + %fieldset + %hr + = link_to "Reset", dashboard_filter_path(entity), class: 'btn right' + diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml index 7c5e9f3fab0..8f66742098a 100644 --- a/app/views/dashboard/_groups.html.haml +++ b/app/views/dashboard/_groups.html.haml @@ -11,10 +11,10 @@ %ul.unstyled - groups.each do |group| %li.wll - = link_to group_path(id: group.code), class: dom_class(group) do + = link_to group_path(id: group.path), class: dom_class(group) do %strong.group_name= truncate(group.name, length: 25) %span.arrow → %span.last_activity %strong Projects: - %span= group.projects.count + %span= group.projects.authorized_for(current_user).count diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 00f19ccdcc6..fac0a074691 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -8,11 +8,23 @@ = link_to new_project_path, class: "btn very_small info" do %i.icon-plus New Project + %ul.nav.nav-projects-tabs + = nav_tab :scope, nil do + = link_to "All", dashboard_path + = nav_tab :scope, 'personal' do + = link_to "Personal", dashboard_path(scope: 'personal') + = nav_tab :scope, 'joined' do + = link_to "Joined", dashboard_path(scope: 'joined') + %ul.unstyled - projects.each do |project| %li.wll = link_to project_path(project), class: dom_class(project) do - %strong.project_name= truncate(project.name, length: 25) + - if project.namespace + = project.namespace.human_name + \/ + %strong.project_name + = truncate(project.name, length: 25) %span.arrow → %span.last_activity diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml index d0882c6dab7..6b360dc1fef 100644 --- a/app/views/dashboard/index.html.haml +++ b/app/views/dashboard/index.html.haml @@ -2,7 +2,6 @@ .projects .activities.span8 = render "events/event_last_push", event: @last_push - = render 'shared/no_ssh' .event_filter = event_filter_link EventFilter.push, 'Push events' diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index 5bd07bcd89f..28bdc5ed814 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -1,9 +1,9 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do - xml.title "#{@user.name} issues" - xml.link :href => dashboard_issues_url(:atom, :private_token => @user.private_token), :rel => "self", :type => "application/atom+xml" - xml.link :href => dashboard_issues_url(:private_token => @user.private_token), :rel => "alternate", :type => "text/html" - xml.id dashboard_issues_url(:private_token => @user.private_token) + xml.title "#{current_user.name} issues" + xml.link :href => dashboard_issues_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml" + xml.link :href => dashboard_issues_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html" + xml.id dashboard_issues_url(:private_token => current_user.private_token) xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index cc488d57e9e..e3093bcfe2a 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -3,17 +3,21 @@ %small (assigned to you) %small.right #{@issues.total_count} issues -%br -.clearfix -- if @issues.any? - - @issues.group_by(&:project).each do |group| - %div.ui-box - - @project = group[0] - %h5= @project.name - %ul.unstyled.issues_table - - group[1].each do |issue| - = render(partial: 'issues/show', locals: {issue: issue}) - %hr - = paginate @issues, theme: "gitlab" -- else - %h3.nothing_here_message Nothing to show here +%hr + +.row + .span3 + = render 'filter', entity: 'issue' + .span9 + - if @issues.any? + - @issues.group_by(&:project).each do |group| + %div.ui-box + - @project = group[0] + %h5= link_to(@project.name, project_path(@project)) + %ul.unstyled.issues_table + - group[1].each do |issue| + = render(partial: 'issues/show', locals: {issue: issue}) + %hr + = paginate @issues, theme: "gitlab" + - else + %p.nothing_here_message Nothing to show here diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 23a7e7222d7..8454cfdc120 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -3,16 +3,20 @@ %small (authored by or assigned to you) %small.right #{@merge_requests.total_count} merge requests -%br -- if @merge_requests.any? - - @merge_requests.group_by(&:project).each do |group| - %ul.unstyled.ui-box - - @project = group[0] - %h5= @project.name - - group[1].each do |merge_request| - = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request}) - %hr - = paginate @merge_requests, theme: "gitlab" +%hr +.row + .span3 + = render 'filter', entity: 'merge_request' + .span9 + - if @merge_requests.any? + - @merge_requests.group_by(&:project).each do |group| + %ul.unstyled.ui-box + - @project = group[0] + %h5= @project.name + - group[1].each do |merge_request| + = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request}) + %hr + = paginate @merge_requests, theme: "gitlab" -- else - %h3.nothing_here_message Nothing to show here + - else + %h3.nothing_here_message Nothing to show here diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index 3b60ed8b0ee..f2d082cb77d 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -1,4 +1,5 @@ -%h1 Access Denied +%h1.http_status_code 403 +%h3.page_title Access Denied %hr -%h2 You are not allowed to access this page. +%p You are not allowed to access this page. %p Read more about project permissions #{link_to "here", help_permissions_path, class: "vlink"} diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index d7b5e68e870..a0aa6306489 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,3 +1,4 @@ -%h1 Encoding Error +%h1.http_status_code 500 +%h3.page_title Encoding Error %hr %p Page can't be loaded because of an encoding error. diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml index cd01ea1b0e6..5c9c4953284 100644 --- a/app/views/errors/git_not_found.html.haml +++ b/app/views/errors/git_not_found.html.haml @@ -1,6 +1,6 @@ -%h1 404 +%h1.http_status_code 404 +%h3.page_title Git Resource Not found %hr -%h2 Git Resource Not found %p Application can't get access to some branch or commit in your repository. It may have been moved. diff --git a/app/views/errors/gitolite.html.haml b/app/views/errors/gitolite.html.haml index 699e6984db6..2670f2d3fda 100644 --- a/app/views/errors/gitolite.html.haml +++ b/app/views/errors/gitolite.html.haml @@ -1,6 +1,6 @@ -%h1 Git Error +%h1.http_status_code 500 +%h3.page_title GitLab was unable to access your Gitolite system. %hr -%h2 GitLab was unable to access your Gitolite system. .git_error_tips %h4 Tips for Administrator: diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index a4e8d0204a9..ee23d2197b4 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,4 +1,4 @@ -%h1 404 +%h1.http_status_code 404 +%h3.page_title The resource you were looking for doesn't exist. %hr -%h2 The resource you were looking for doesn't exist. %p You may have mistyped the address or the page may have moved. diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 0d91a67a60d..2446b764e4d 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -1,7 +1,7 @@ - if event.allowed? %div.event-item = event_image(event) - = image_tag gravatar_icon(event.author_email), class: "avatar" + = image_tag gravatar_icon(event.author_email), class: "avatar s24" - if event.push? = render "events/event/push", event: event diff --git a/app/views/groups/_new_member.html.haml b/app/views/groups/_new_member.html.haml new file mode 100644 index 00000000000..f48c2c23d83 --- /dev/null +++ b/app/views/groups/_new_member.html.haml @@ -0,0 +1,18 @@ += form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f| + %fieldset + %legend= "New Team member(s) for #{@project.name}" + + %h6 1. Choose people you want in the team + .clearfix + = f.label :user_ids, "People" + .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true}) + + %h6 2. Set access level for them + .clearfix + = f.label :project_access, "Project Access" + .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" + + .form-actions + = hidden_field_tag :redirect_to, people_group_path(@group, project_id: @project.id) + = f.submit 'Add', class: "btn save-btn" + diff --git a/app/views/groups/_people_filter.html.haml b/app/views/groups/_people_filter.html.haml new file mode 100644 index 00000000000..79a1b01a5fa --- /dev/null +++ b/app/views/groups/_people_filter.html.haml @@ -0,0 +1,14 @@ += form_tag people_group_path(@group), method: 'get' do + %fieldset + %legend Projects: + %ul.nav.nav-pills.nav-stacked + - @projects.each do |project| + %li{class: ("active" if params[:project_id] == project.id.to_s)} + = link_to people_group_path(@group, project_id: project.id) do + = project.name_with_namespace + %small.right= project.users.count + + %fieldset + %hr + = link_to "Reset", people_group_path(@group), class: 'btn right' + diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index b565dad37a9..39c0b6af685 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -3,7 +3,14 @@ Projects %small (#{projects.count}) + - if can? current_user, :manage_group, @group + %span.right + = link_to new_project_path(namespace_id: @group.id), class: "btn very_small info" do + %i.icon-plus + New Project %ul.unstyled + - if projects.blank? + %p.nothing_here_message This groups has no projects yet - projects.each do |project| %li.wll = link_to project_path(project), class: dom_class(project) do diff --git a/app/views/groups/people.html.haml b/app/views/groups/people.html.haml index 258108089c3..68102b6a85a 100644 --- a/app/views/groups/people.html.haml +++ b/app/views/groups/people.html.haml @@ -1,12 +1,20 @@ -.ui-box - %h5 - People - %small - (#{@users.size}) - %ul.unstyled - - @users.each do |user| - %li.wll - = image_tag gravatar_icon(user.email, 16), class: "avatar s16" - %strong= user.name - %span.cgray= user.email +.row + .span3 + = render 'people_filter' + .span9 + - if @project && can?(current_user, :manage_group, @group) + = render "new_member" + .ui-box + %h5 + Team + %small + (#{@users.size}) + %ul.unstyled + - @users.each do |user| + %li.wll + = image_tag gravatar_icon(user.email, 16), class: "avatar s16" + %strong= user.name + %span.cgray= user.email + - if @group.owner == user + %span.btn.btn-small.disabled.right Group Owner diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 72d7ad9a592..b929b267e8b 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -6,11 +6,10 @@ %span.cgray Events and projects are filtered in scope of group %hr - = render 'shared/no_ssh' - if @events.any? .content_list= render @events - else - %h4.nothing_here_message Projects activity will be displayed here + %p.nothing_here_message Projects activity will be displayed here .loading.hide .side = render "projects", projects: @projects diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml index 00085166bcf..3f16637dd2e 100644 --- a/app/views/help/api.html.haml +++ b/app/views/help/api.html.haml @@ -21,6 +21,8 @@ = link_to "Issues", "#issues", 'data-toggle' => 'tab' %li = link_to "Milestones", "#milestones", 'data-toggle' => 'tab' + %li + = link_to "Notes", "#notes", 'data-toggle' => 'tab' .tab-content .tab-pane.active#README @@ -94,3 +96,12 @@ .file_content.wiki = preserve do = markdown File.read(Rails.root.join("doc", "api", "milestones.md")) + + .tab-pane#notes + .file_holder + .file_title + %i.icon-file + Notes + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "notes.md")) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 25fe9d806bc..4a0f60d36c2 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -2,7 +2,7 @@ %meta{charset: "utf-8"} %title GitLab - = " > #{@project.name}" if @project && !@project.new_record? + = " > #{title}" if defined?(title) = favicon_link_tag 'favicon.ico' = stylesheet_link_tag "application" = javascript_include_tag "application" diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 38e1d7f0597..8fbec43f4a1 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -18,7 +18,7 @@ %li = link_to profile_path, title: "Your Profile", class: 'has_bottom_tooltip', 'data-original-title' => 'Your profile' do %i.icon-user - %span.separator + %li.separator %li = render "layouts/search" %li diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 502f289ec05..7b2a291d05c 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,6 +1,6 @@ :javascript $(function() { - GitLab.GfmAutoComplete.Members.url = "#{ "/api/v2/projects/#{@project.code}/members" if @project }"; + GitLab.GfmAutoComplete.Members.url = "#{ "/api/v2/projects/#{@project.path}/members" if @project }"; GitLab.GfmAutoComplete.Members.params.private_token = "#{current_user.private_token}"; GitLab.GfmAutoComplete.Emoji.data = #{raw emoji_autocomplete_source}; diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 582f86ba32c..6b643ec8ccb 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "Admin area" %body{class: "#{app_theme} admin"} = render "layouts/flash" = render "layouts/head_panel", title: "Admin area" @@ -8,8 +8,10 @@ %ul.main_menu = nav_link(controller: :dashboard, html_options: {class: 'home'}) do = link_to "Stats", admin_root_path - = nav_link(controller: [:projects, :groups]) do + = nav_link(controller: :projects) do = link_to "Projects", admin_projects_path + = nav_link(controller: :groups) do + = link_to "Groups", admin_groups_path = nav_link(controller: :users) do = link_to "Users", admin_users_path = nav_link(controller: :logs) do diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 40f4f88cbce..a41de538436 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "Dashboard" %body{class: "#{app_theme} application"} = render "layouts/flash" = render "layouts/head_panel", title: "Dashboard" diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index 1f5c03bdced..3554d88f10c 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "Error" %body{class: "#{app_theme} application"} = render "layouts/flash" = render "layouts/head_panel", title: "" diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 985200e2539..d40d9525bb8 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "#{@group.name}" %body{class: "#{app_theme} application"} = render "layouts/flash" = render "layouts/head_panel", title: "#{@group.name}" diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 7b79897b653..35bf5577e1c 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -4,7 +4,7 @@ %title GitLab :css - .header h1 {color: #BBBBBB !important; font: bold 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;} + .header h1 {color: #BBBBBB !important; font: bold 22px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 32px;} .header p {color: #c6c6c6; font: normal 12px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 18px;} .content h2 {color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; } .content p {color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif;} @@ -20,7 +20,7 @@ %td{style: "font-size: 0px;", width: "20"} \ %td{align: "left", style: "padding: 18px 0 10px;", width: "580"} - %h1{style: "color: #BBBBBB; font: normal 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;"} + %h1{style: "color: #BBBBBB; font: normal 22px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 32px;"} GITLAB - if @project | #{@project.name} @@ -35,5 +35,5 @@ %p{style: "font-size: 11px; color:#7d7a7a; margin: 0; padding: 0; font-family: Helvetica, Arial, sans-serif;"} You're receiving this notification because you are a member of the - if @project - #{@project.name} + #{@project.name} project team. diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 7a54bb7cf2f..b2743222281 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "Profile" %body{class: "#{app_theme} profile"} = render "layouts/flash" = render "layouts/head_panel", title: "Profile" diff --git a/app/views/layouts/project_resource.html.haml b/app/views/layouts/project_resource.html.haml index b1dbe41ce65..ab8e88c07cd 100644 --- a/app/views/layouts/project_resource.html.haml +++ b/app/views/layouts/project_resource.html.haml @@ -1,13 +1,15 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: @project.name %body{class: "#{app_theme} project"} = render "layouts/flash" = render "layouts/head_panel", title: @project.name + - if can?(current_user, :download_code, @project) + = render 'shared/no_ssh' .container %ul.main_menu = nav_link(html_options: {class: "home #{project_tab_class}"}) do - = link_to @project.code, project_path(@project), title: "Project" + = link_to @project.path, project_path(@project), title: "Project" - if @project.repo_exists? - if can? current_user, :download_code, @project diff --git a/app/views/merge_requests/show/_diffs.html.haml b/app/views/merge_requests/show/_diffs.html.haml index 7685090311a..0807454c4b0 100644 --- a/app/views/merge_requests/show/_diffs.html.haml +++ b/app/views/merge_requests/show/_diffs.html.haml @@ -1,8 +1,10 @@ - if @merge_request.valid_diffs? = render "commits/diffs", diffs: @diffs - elsif @merge_request.broken_diffs? - %h4.nothing_here_message + %h4.nothing_here_message Can't load diff. - You can #{link_to "download MR patch", raw_project_merge_request_path(@project, @merge_request), class: "vlink"} instead. + You can + = link_to "download it", project_merge_request_path(@project, @merge_request), format: :diff, class: "vlink" + instead. - else %h4.nothing_here_message Nothing to merge diff --git a/app/views/merge_requests/show/_mr_title.html.haml b/app/views/merge_requests/show/_mr_title.html.haml index 8708469cc5d..a5275650d86 100644 --- a/app/views/merge_requests/show/_mr_title.html.haml +++ b/app/views/merge_requests/show/_mr_title.html.haml @@ -13,9 +13,14 @@ = "MERGED" - if can?(current_user, :modify_merge_request, @merge_request) - if @merge_request.open? - = link_to raw_project_merge_request_path(@project, @merge_request), class: "btn grouped" do - %i.icon-download-alt - Get Patch + .left.btn-group + %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} } + %i.icon-download-alt + Download as + %span.caret + %ul.dropdown-menu + %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: {closed: true }, status_only: true), method: :put, class: "btn grouped danger", title: "Close merge request" diff --git a/app/views/notify/note_wiki_email.html.haml b/app/views/notify/note_wiki_email.html.haml deleted file mode 100644 index 41a237fc53e..00000000000 --- a/app/views/notify/note_wiki_email.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"} - %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"} - %tr - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - %td{align: "left", style: "padding: 20px 0 0;"} - %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} - New comment for Wiki page - = link_to_gfm @wiki.title, project_wiki_url(@wiki.project, @wiki, anchor: "note_#{@note.id}") - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - %tr - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - %td{style: "padding: 15px 0 15px;", valign: "top"} - %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "} - %a{href: "#", style: "color: #0eb6ce; text-decoration: none;"} #{@note.author_name} - commented on Wiki page: - %br - %table{border: "0", cellpadding: "0", cellspacing: "0", width: "558"} - %tr - %td{valign: "top"} - %div{ style: "background:#f5f5f5; padding:20px;border:1px solid #ddd" } - = markdown(@note.note) - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - diff --git a/app/views/profile/account.html.haml b/app/views/profile/account.html.haml index 1e3a8b1a0d4..e2c5bcdb8e2 100644 --- a/app/views/profile/account.html.haml +++ b/app/views/profile/account.html.haml @@ -8,6 +8,7 @@ = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) + %fieldset %legend Private token @@ -41,14 +42,28 @@ .clearfix = f.label :password - .input= f.password_field :password + .input= f.password_field :password, required: true .clearfix = f.label :password_confirmation - .input= f.password_field :password_confirmation - .actions - = f.submit 'Save', class: "btn save-btn" + .input + = f.password_field :password_confirmation, required: true + .clearfix + .input + = f.submit 'Save password', class: "btn save-btn" +%fieldset + %legend + Username + %small.right + Changing your username can have unintended side effects! + = form_for @user, url: profile_update_path, method: :put do |f| + .padded + = f.label :username + .input + = f.text_field :username, required: true + .input + = f.submit 'Save username', class: "btn save-btn" diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml index 461f4feaf61..2962ad980b3 100644 --- a/app/views/projects/_clone_panel.html.haml +++ b/app/views/projects/_clone_panel.html.haml @@ -6,12 +6,12 @@ .right - unless @project.empty_repo? - if can? current_user, :download_code, @project - = link_to archive_project_repository_path(@project), class: "btn grouped" do + = link_to archive_project_repository_path(@project), class: "btn-small btn grouped" do %i.icon-download-alt Download - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn grouped" do + = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn-small btn grouped" do Merge Request - if @project.issues_enabled && can?(current_user, :write_issue, @project) - = link_to new_project_issue_path(@project), title: "New Issue", class: "btn grouped" do + = link_to new_project_issue_path(@project), title: "New Issue", class: "btn-small btn grouped" do Issue diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml index 9ee65942fe9..5c60132cc82 100644 --- a/app/views/projects/_form.html.haml +++ b/app/views/projects/_form.html.haml @@ -9,48 +9,45 @@ Project name is .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - %fieldset %legend Advanced settings: - .clearfix + .control-group = f.label :path do - Path - .input - .input-prepend - %strong - = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true - .clearfix - = f.label :code do - URL - .input - .input-prepend - %span.add-on= web_app_url - = f.text_field :code, placeholder: "example" - - - unless @project.new_record? || @project.heads.empty? + Repository + .controls + = text_field_tag :ppath, @project.path_to_repo, class: "xxlarge", readonly: true + + .control-group + = f.label :namespace_id do + %span Namespace + .controls + = f.select :namespace_id, namespaces_options(@project.namespace_id), {prompt: 'Choose a project namespace'}, {class: 'chosen'} + + %span.cred Be careful. Changing project namespace can have unintended side effects + + - unless @project.heads.empty? .clearfix = f.label :default_branch, "Default Branch" .input= f.select(:default_branch, @project.heads.map(&:name), {}, style: "width:210px;") - - unless @project.new_record? - %fieldset - %legend Features: + %fieldset + %legend Features: - .clearfix - = f.label :issues_enabled, "Issues" - .input= f.check_box :issues_enabled + .clearfix + = f.label :issues_enabled, "Issues" + .input= f.check_box :issues_enabled - .clearfix - = f.label :merge_requests_enabled, "Merge Requests" - .input= f.check_box :merge_requests_enabled + .clearfix + = f.label :merge_requests_enabled, "Merge Requests" + .input= f.check_box :merge_requests_enabled - .clearfix - = f.label :wall_enabled, "Wall" - .input= f.check_box :wall_enabled + .clearfix + = f.label :wall_enabled, "Wall" + .input= f.check_box :wall_enabled - .clearfix - = f.label :wiki_enabled, "Wiki" - .input= f.check_box :wiki_enabled + .clearfix + = f.label :wiki_enabled, "Wiki" + .input= f.check_box :wiki_enabled %br diff --git a/app/views/projects/_new_form.html.haml b/app/views/projects/_new_form.html.haml index e6d5e93fca7..2ef29cb0c65 100644 --- a/app/views/projects/_new_form.html.haml +++ b/app/views/projects/_new_form.html.haml @@ -9,21 +9,12 @@ = f.text_field :name, placeholder: "Example Project", class: "xxlarge" = f.submit 'Create project', class: "btn primary project-submit" - %hr - %div.adv_settings - %h6 Advanced settings: - .clearfix - = f.label :path do - Git Clone - .input - .input-prepend - %span.add-on= Gitlab.config.ssh_path - = f.text_field :path, placeholder: "example_project", disabled: !@project.new_record? - %span.add-on= ".git" + - if current_user.several_namespaces? .clearfix - = f.label :code do - URL + = f.label :namespace_id do + %span.cgray Namespace .input - .input-prepend - %span.add-on= web_app_url - = f.text_field :code, placeholder: "example" + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen'} + %hr + %p.padded + All created project are private. You choose who can see project and commit to repository. diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index d9a151fc706..f331ae7fadc 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,31 +1,33 @@ -= render 'shared/no_ssh' = render 'clone_panel' %div.git-empty - %h4 Git global setup: - %pre.dark - = preserve do - git config --global user.name "#{current_user.name}" - git config --global user.email "#{current_user.email}" + %fieldset + %legend Git global setup: + %pre.dark + = preserve do + git config --global user.name "#{current_user.name}" + git config --global user.email "#{current_user.email}" - %h4.prepend-top-20 Create Repository - %pre.dark - = preserve do - mkdir #{@project.path} - cd #{@project.path} - git init - touch README - git add README - git commit -m 'first commit' - git remote add origin #{@project.url_to_repo} - git push -u origin master + %fieldset + %legend Create Repository + %pre.dark + = preserve do + mkdir #{@project.path} + cd #{@project.path} + git init + touch README + git add README + git commit -m 'first commit' + git remote add origin #{@project.url_to_repo} + git push -u origin master - %h4.prepend-top-20 Existing Git Repo? - %pre.dark - = preserve do - cd existing_git_repo - git remote add origin #{@project.url_to_repo} - git push -u origin master + %fieldset + %legend Existing Git Repo? + %pre.dark + = preserve do + cd existing_git_repo + git remote add origin #{@project.url_to_repo} + git push -u origin master - if can? current_user, :admin_project, @project .prepend-top-20 diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index 8aaa0e491dd..f44ed529182 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -1,6 +1,6 @@ - if @project.valid? :plain - location.href = "#{edit_project_path(@project, notice: 'Project was successfully updated.')}"; + location.href = "#{edit_project_path(@project)}"; - else :plain $('.project_edit_holder').show(); diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 0d5f545850a..8448193deb9 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -23,7 +23,7 @@ %tr %td = link_to project do - %strong.term= project.name + %strong.term= project.name_with_namespace %small.cgray last activity at = project.last_activity_date.stamp("Aug 25, 2011") diff --git a/app/views/services/_gitlab_ci.html.haml b/app/views/services/_gitlab_ci.html.haml index 3c9820b32b4..4c1ec5bc348 100644 --- a/app/views/services/_gitlab_ci.html.haml +++ b/app/views/services/_gitlab_ci.html.haml @@ -25,7 +25,7 @@ = f.check_box :active .control-group - = f.label :active, "Project URL", class: "control-label" + = f.label :project_url, "Project URL", class: "control-label" .controls = f.text_field :project_url, class: "input-xlarge", placeholder: "http://ci.gitlabhq.com/projects/3" diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 924eb3fcae4..f632e1221f9 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,4 +1,4 @@ .input-prepend.project_clone_holder %button{class: "btn active", :"data-clone" => @project.ssh_url_to_repo} SSH %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= Gitlab.config.web_protocol.upcase - = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5" + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge" diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index c75a1d93b70..5fdcea850b2 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,3 +1,3 @@ - if current_user.require_ssh_key? - %p.error_message - You won't be able to pull or push project code until you #{link_to 'add an SSH key', new_key_path} to your profile + %p.error_message.centered + You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_key_path} to your profile diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml new file mode 100644 index 00000000000..ed518300ac0 --- /dev/null +++ b/app/views/snippets/_blob.html.haml @@ -0,0 +1,12 @@ +.file_holder + .file_title + %i.icon-file + %strong= @snippet.file_name + %span.options + = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn very_small", target: "_blank" + .file_content.code + - unless @snippet.content.empty? + %div{class: user_color_scheme_class} + = raw @snippet.colorize(formatter: :gitlab) + - else + %p.nothing_here_message Empty file diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index e61e61a7e5e..981c7cf0c12 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -1,27 +1,27 @@ -%h3= @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}" +%h3.page_title + = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}" %hr = form_for [@project, @snippet] do |f| - %table.no-borders - -if @snippet.errors.any? - .alert-message.block-message.error - %ul - - @snippet.errors.full_messages.each do |msg| - %li= msg + -if @snippet.errors.any? + .alert-message.block-message.error + %ul + - @snippet.errors.full_messages.each do |msg| + %li= msg - .clearfix - = f.label :title - .input= f.text_field :title, placeholder: "Example Snippet" - .clearfix - = f.label :file_name - .input= f.text_field :file_name, placeholder: "example.rb" - .clearfix - = f.label "Lifetime" - .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'} - .clearfix - = f.label :content, "Code" - .input= f.text_area :content, class: "span8" + .clearfix + = f.label :title + .input= f.text_field :title, placeholder: "Example Snippet" + .clearfix + = f.label :file_name + .input= f.text_field :file_name, placeholder: "example.rb" + .clearfix + = f.label "Lifetime" + .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'} + .clearfix + = f.label :content, "Code" + .input= f.text_area :content, class: "span8" - .actions + .form-actions = f.submit 'Save', class: "primary btn" = link_to "Cancel", project_snippets_path(@project), class: " btn" - unless @snippet.new_record? diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 1b8701e91ed..f3e01928077 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -7,17 +7,5 @@ = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn small right" %br -%div - .file_holder - .file_title - %i.icon-file - %strong= @snippet.file_name - %span.options - = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn very_small", target: "_blank" - .file_content.code - %div{class: current_user.dark_scheme ? "black" : ""} - = raw @snippet.colorize(options: { linenos: 'True'}) - - -%div - = render "notes/notes_with_form", tid: @snippet.id, tt: "snippet" +%div= render 'blob' +%div#notes= render "notes/notes_with_form", tid: @snippet.id, tt: "snippet" diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml index f68f8eb471f..8938c7d8a6d 100644 --- a/app/views/team_members/_show.html.haml +++ b/app/views/team_members/_show.html.haml @@ -1,26 +1,28 @@ - user = member.user - allow_admin = can? current_user, :admin_project, @project -%tr{id: dom_id(member), class: "team_member_row user_#{user.id}"} - %td.span6 - = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do - = image_tag gravatar_icon(user.email, 40), class: "avatar s32" - = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do - %strong= truncate(user.name, lenght: 40) - %br - %small.cgray= user.email +%li.wll{id: dom_id(member), class: "team_member_row user_#{user.id}"} + .row + .span6 + = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do + = image_tag gravatar_icon(user.email, 40), class: "avatar s32" + = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do + %strong= truncate(user.name, lenght: 40) + %br + %small.cgray= user.email - %td.span5 - .right - - if current_user == user - %span.btn.disabled This is you! - - if @project.owner == user - %span.btn.disabled.success Owner - - elsif user.blocked - %span.btn.disabled.blocked Blocked - - elsif allow_admin - = link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do - %i.icon-minus.icon-white + .span5.right + - if allow_admin + .left + = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| + = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" + .right + - if current_user == user + %span.btn.disabled This is you! + - if @project.namespace_owner == user + %span.btn.disabled.success Owner + - elsif user.blocked + %span.btn.disabled.blocked Blocked + - elsif allow_admin + = link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do + %i.icon-minus.icon-white - - if allow_admin - = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" diff --git a/app/views/team_members/_team.html.haml b/app/views/team_members/_team.html.haml index 26d13533b5c..65f17864814 100644 --- a/app/views/team_members/_team.html.haml +++ b/app/views/team_members/_team.html.haml @@ -1,11 +1,9 @@ - grouper_project_members(@project).each do |access, members| - %table.low - %thead - %tr - %th.span7 - = Project.access_options.key(access).pluralize - %th - %tbody + %fieldset + %legend + = Project.access_options.key(access).pluralize + %small= members.size + %ul.unstyled - members.each do |up| = render(partial: 'team_members/show', locals: {member: up}) diff --git a/app/views/team_members/index.html.haml b/app/views/team_members/index.html.haml index ca3edcf7ffb..e413c81bb6c 100644 --- a/app/views/team_members/index.html.haml +++ b/app/views/team_members/index.html.haml @@ -1,18 +1,20 @@ = render "projects/project_head" %h3.page_title Team Members - %small (#{@project.users_projects.count}) - -- if can? current_user, :admin_team_member, @project - %p.slead + (#{@project.users_projects.count}) + %small Read more about project permissions %strong= link_to "here", help_permissions_path, class: "vlink" + - if can? current_user, :admin_team_member, @project %span.right = link_to import_project_team_members_path(@project), class: "btn small grouped", title: "Import team from another project" do Import team from another project = link_to new_project_team_member_path(@project), class: "btn success small grouped", title: "New Team Member" do New Team Member +%hr + - .clearfix -= render partial: "team_members/team", locals: {project: @project} +.clearfix +%div.team-table + = render partial: "team_members/team", locals: {project: @project} diff --git a/app/views/tree/_head.html.haml b/app/views/tree/_head.html.haml index f8e5c99f06a..f14526cf23a 100644 --- a/app/views/tree/_head.html.haml +++ b/app/views/tree/_head.html.haml @@ -4,4 +4,4 @@ = nav_link(controller: :tree) do = link_to 'Source', project_tree_path(@project, @ref) %li.right - = render "shared/clone_panel"
\ No newline at end of file + = render "shared/clone_panel" diff --git a/app/views/tree/blob/_text.html.haml b/app/views/tree/blob/_text.html.haml index 9e0f4bc4bc1..122e275219d 100644 --- a/app/views/tree/blob/_text.html.haml +++ b/app/views/tree/blob/_text.html.haml @@ -8,8 +8,7 @@ - else .file_content.code - unless blob.empty? - %div{class: current_user.dark_scheme ? "black" : "white"} - = preserve do - = raw blob.colorize(formatter: :gitlab) + %div{class: user_color_scheme_class} + = raw blob.colorize(formatter: :gitlab) - else - %h4.nothing_here_message Empty file + %p.nothing_here_message Empty file diff --git a/app/views/wikis/show.html.haml b/app/views/wikis/show.html.haml index 579ea1b3ad6..c3074539054 100644 --- a/app/views/wikis/show.html.haml +++ b/app/views/wikis/show.html.haml @@ -19,6 +19,3 @@ - if can? current_user, :admin_wiki, @project = link_to project_wiki_path(@project, @wiki), confirm: "Are you sure you want to delete this page?", method: :delete do Delete this page - -%hr -.wiki_notes#notes= render "notes/notes_with_form", tid: @wiki.id, tt: "wiki" diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 10128660cd4..4f4f69c4ece 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -7,8 +7,8 @@ class PostReceive # Ignore push from non-gitlab users user = if identifier.eql? Gitlab.config.gitolite_admin_key - email = project.commit(newrev).author.email - User.find_by_email(email) + email = project.commit(newrev).author.email rescue nil + User.find_by_email(email) if email elsif /^[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,4}$/.match(identifier) User.find_by_email(identifier) else diff --git a/config/database.yml.sqlite b/config/database.yml.sqlite deleted file mode 100644 index 591448f6bee..00000000000 --- a/config/database.yml.sqlite +++ /dev/null @@ -1,31 +0,0 @@ -# -# PRODUCTION -# -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -production: - adapter: sqlite3 - database: db/production.sqlite3 - pool: 5 - timeout: 5000 - -# -# Development specific -# -development: - adapter: sqlite3 - database: db/development.sqlite3 - pool: 5 - timeout: 5000 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: &test - adapter: sqlite3 - database: db/test.sqlite3 - pool: 5 - timeout: 5000 diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 3549b8362bb..8f8bef42bef 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -4,4 +4,5 @@ # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone -Mime::Type.register_alias 'text/plain', :patch +Mime::Type.register_alias "text/plain", :diff +Mime::Type.register_alias "text/plain", :patch diff --git a/config/routes.rb b/config/routes.rb index 98cf7e812c9..9c58ce17fc2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,7 +18,7 @@ Gitlab::Application.routes.draw do project_root: Gitlab.config.git_base_path, upload_pack: Gitlab.config.git_upload_pack, receive_pack: Gitlab.config.git_receive_pack - }), at: '/:path', constraints: { path: /[\w\.-]+\.git/ } + }), at: '/:path', constraints: { path: /[-\/\w\.-]+\.git/ } # # Help @@ -49,7 +49,7 @@ Gitlab::Application.routes.draw do delete :remove_project end end - resources :projects, constraints: { id: /[^\/]+/ } do + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do member do get :team put :team_update @@ -107,7 +107,7 @@ Gitlab::Application.routes.draw do # # Project Area # - resources :projects, constraints: { id: /[^\/]+/ }, except: [:new, :create, :index], path: "/" do + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do member do get "wall" get "graph" @@ -159,12 +159,11 @@ Gitlab::Application.routes.draw do end end - resources :merge_requests do + resources :merge_requests, constraints: {id: /\d+/} do member do get :diffs get :automerge get :automerge_check - get :raw end collection do diff --git a/db/fixtures/development/001_admin.rb b/db/fixtures/development/001_admin.rb index c857f6bcb3f..fbe41e4d22d 100644 --- a/db/fixtures/development/001_admin.rb +++ b/db/fixtures/development/001_admin.rb @@ -1,21 +1,11 @@ -unless User.count > 0 - admin = User.create( - :email => "admin@local.host", - :name => "Administrator", - :password => "5iveL!fe", - :password_confirmation => "5iveL!fe" - ) - - admin.projects_limit = 10000 - admin.admin = true - admin.save! - - if admin.valid? - puts %q[ - Administrator account created: - - login.........admin@local.host - password......5iveL!fe - ] - end -end +User.seed(:id, [ + { + id: 1, + name: "Administrator", + email: "admin@local.host", + username: 'root', + password: "5iveL!fe", + password_confirmation: "5iveL!fe", + admin: true, + } +]) diff --git a/db/fixtures/development/002_project.rb b/db/fixtures/development/002_project.rb index eb68b5fe93a..91d42a14201 100644 --- a/db/fixtures/development/002_project.rb +++ b/db/fixtures/development/002_project.rb @@ -1,5 +1,5 @@ Project.seed(:id, [ - { id: 1, name: "Underscore.js", path: "underscore", code: "underscore", owner_id: 1 }, - { id: 2, name: "Diaspora", path: "diaspora", code: "diaspora", owner_id: 1 }, - { id: 3, name: "Ruby on Rails", path: "rails", code: "rails", owner_id: 1 } + { id: 1, name: "Underscore.js", path: "underscore", owner_id: 1, namespace_id: 1 }, + { id: 2, name: "Diaspora", path: "diaspora", owner_id: 1 }, + { id: 3, name: "Ruby on Rails", path: "rails", owner_id: 1 } ]) diff --git a/db/fixtures/development/003_users.rb b/db/fixtures/development/003_users.rb index 309eb90b1bf..25705f1b726 100644 --- a/db/fixtures/development/003_users.rb +++ b/db/fixtures/development/003_users.rb @@ -1,11 +1,11 @@ User.seed(:id, [ - { :id => 2, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 3, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 4, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 5, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 6, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 7, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 8, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 9, :name => Faker::Internet.user_name, :email => Faker::Internet.email} + { id: 2, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 3, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 4, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 5, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 6, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 7, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 8, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 9, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email} ]) diff --git a/db/fixtures/development/009_source_code.rb b/db/fixtures/development/009_source_code.rb index 489bd02ea32..849d1aab8ae 100644 --- a/db/fixtures/development/009_source_code.rb +++ b/db/fixtures/development/009_source_code.rb @@ -1,7 +1,7 @@ root = Gitlab.config.git_base_path projects = [ - { path: 'underscore.git', git: 'https://github.com/documentcloud/underscore.git' }, + { path: 'root/underscore.git', git: 'https://github.com/documentcloud/underscore.git' }, { path: 'diaspora.git', git: 'https://github.com/diaspora/diaspora.git' }, { path: 'rails.git', git: 'https://github.com/rails/rails.git' }, ] @@ -13,9 +13,10 @@ projects.each do |project| next if File.exists?(project_path) cmds = [ - "cd #{root} && sudo -u git -H git clone --bare #{project[:git]}", + "cd #{root} && sudo -u git -H git clone --bare #{project[:git]} ./#{project[:path]}", "sudo cp ./lib/hooks/post-receive #{project_path}/hooks/post-receive", - "sudo chown git:git #{project_path}/hooks/post-receive" + "sudo chown git:git -R #{project_path}", + "sudo chmod 770 -R #{project_path}", ] cmds.each do |cmd| diff --git a/db/fixtures/development/010_groups.rb b/db/fixtures/development/010_groups.rb new file mode 100644 index 00000000000..09371b00751 --- /dev/null +++ b/db/fixtures/development/010_groups.rb @@ -0,0 +1,11 @@ +Group.seed(:id, [ + { id: 100, name: "Gitlab", path: 'gitlab', owner_id: 1}, + { id: 101, name: "Rails", path: 'rails', owner_id: 1 }, + { id: 102, name: "KDE", path: 'kde', owner_id: 1 } +]) + +Project.seed(:id, [ + { id: 10, name: "kdebase", path: "kdebase", owner_id: 1, namespace_id: 102 }, + { id: 11, name: "kdelibs", path: "kdelibs", owner_id: 1, namespace_id: 102 }, + { id: 12, name: "amarok", path: "amarok", owner_id: 1, namespace_id: 102 } +]) diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index cfff6bf8bc2..f119694d11d 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -1,8 +1,9 @@ admin = User.create( - :email => "admin@local.host", - :name => "Administrator", - :password => "5iveL!fe", - :password_confirmation => "5iveL!fe" + email: "admin@local.host", + name: "Administrator", + username: 'root', + password: "5iveL!fe", + password_confirmation: "5iveL!fe" ) admin.projects_limit = 10000 diff --git a/db/migrate/20121122145155_convert_group_to_namespace.rb b/db/migrate/20121122145155_convert_group_to_namespace.rb new file mode 100644 index 00000000000..fc8b023d814 --- /dev/null +++ b/db/migrate/20121122145155_convert_group_to_namespace.rb @@ -0,0 +1,13 @@ +class ConvertGroupToNamespace < ActiveRecord::Migration + def up + rename_table 'groups', 'namespaces' + add_column :namespaces, :type, :string, null: true + + # Migrate old groups + Namespace.update_all(type: 'Group') + end + + def down + raise 'Rollback is not allowed' + end +end diff --git a/db/migrate/20121122150932_add_namespace_id_to_project.rb b/db/migrate/20121122150932_add_namespace_id_to_project.rb new file mode 100644 index 00000000000..904f3aa32be --- /dev/null +++ b/db/migrate/20121122150932_add_namespace_id_to_project.rb @@ -0,0 +1,5 @@ +class AddNamespaceIdToProject < ActiveRecord::Migration + def change + rename_column :projects, :group_id, :namespace_id + end +end diff --git a/db/migrate/20121123104937_add_username_to_user.rb b/db/migrate/20121123104937_add_username_to_user.rb new file mode 100644 index 00000000000..04232a119d9 --- /dev/null +++ b/db/migrate/20121123104937_add_username_to_user.rb @@ -0,0 +1,5 @@ +class AddUsernameToUser < ActiveRecord::Migration + def change + add_column :users, :username, :string, null: true + end +end diff --git a/db/migrate/20121123164910_rename_code_to_path.rb b/db/migrate/20121123164910_rename_code_to_path.rb new file mode 100644 index 00000000000..fb10baf58cf --- /dev/null +++ b/db/migrate/20121123164910_rename_code_to_path.rb @@ -0,0 +1,11 @@ +class RenameCodeToPath < ActiveRecord::Migration + def up + remove_column :projects, :code + rename_column :namespaces, :code, :path + end + + def down + add_column :projects, :code, :string + rename_column :namespaces, :path, :code + end +end diff --git a/db/schema.rb b/db/schema.rb index 27b1f4aa84a..32dafed2b63 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20121120113838) do +ActiveRecord::Schema.define(:version => 20121123164910) do create_table "events", :force => true do |t| t.string "target_type" @@ -25,14 +25,6 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.integer "author_id" end - create_table "groups", :force => true do |t| - t.string "name", :null => false - t.string "code", :null => false - t.integer "owner_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - create_table "issues", :force => true do |t| t.string "title" t.integer "assignee_id" @@ -88,6 +80,15 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.datetime "updated_at", :null => false end + create_table "namespaces", :force => true do |t| + t.string "name", :null => false + t.string "path", :null => false + t.integer "owner_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "type" + end + create_table "notes", :force => true do |t| t.text "note" t.string "noteable_id" @@ -110,14 +111,13 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.boolean "private_flag", :default => true, :null => false - t.string "code" t.integer "owner_id" t.string "default_branch" t.boolean "issues_enabled", :default => true, :null => false t.boolean "wall_enabled", :default => true, :null => false t.boolean "merge_requests_enabled", :default => true, :null => false t.boolean "wiki_enabled", :default => true, :null => false - t.integer "group_id" + t.integer "namespace_id" end create_table "protected_branches", :force => true do |t| @@ -194,6 +194,7 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.datetime "locked_at" t.string "extern_uid" t.string "provider" + t.string "username" end add_index "users", ["email"], :name => "index_users_on_email", :unique => true diff --git a/doc/api/notes.md b/doc/api/notes.md new file mode 100644 index 00000000000..97899fa0f6e --- /dev/null +++ b/doc/api/notes.md @@ -0,0 +1,136 @@ +## List notes + +### List project wall notes + +Get a list of project wall notes. + +``` +GET /projects/:id/notes +``` + +```json +[ + { + "id": 522, + "body": "The solution is rather tricky", + "author": { + "id": 1, + "email": "john@example.com", + "name": "John Smith", + "blocked": false, + "created_at": "2012-05-23T08:00:58Z" + }, + "created_at": "2012-11-27T19:16:44Z" + } +] +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project + +### List issue notes + +Get a list of issue notes. + +``` +GET /projects/:id/issues/:issue_id/notes +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `issue_id` (required) - The ID of an issue + +### List snippet notes + +Get a list of snippet notes. + +``` +GET /projects/:id/snippets/:snippet_id/notes +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a snippet + +## Single note + +### Single issue note + +Get an issue note. + +``` +GET /projects/:id/issues/:issue_id/:notes/:note_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `issue_id` (required) - The ID of a project issue ++ `note_id` (required) - The ID of an issue note + +### Single snippet note + +Get a snippet note. + +``` +GET /projects/:id/issues/:snippet_id/:notes/:note_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project snippet ++ `note_id` (required) - The ID of an snippet note + +## New note + +### New wall note + +Create a new wall note. + +``` +POST /projects/:id/notes +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `body` (required) - The content of a note + +Will return created note with status `201 Created` on success, or `404 Not found` on fail. + + +### New issue note + +Create a new issue note. + +``` +POST /projects/:id/issues/:issue_id/notes +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `issue_id` (required) - The ID of an issue ++ `body` (required) - The content of a note + +Will return created note with status `201 Created` on success, or `404 Not found` on fail. + +### New snippet note + +Create a new snippet note. + +``` +POST /projects/:id/snippets/:snippet_id/notes +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of an snippet ++ `body` (required) - The content of a note + +Will return created note with status `201 Created` on success, or `404 Not found` on fail. diff --git a/doc/install/databases.md b/doc/install/databases.md index b7beff26a0f..1a6f739ecdc 100644 --- a/doc/install/databases.md +++ b/doc/install/databases.md @@ -1,12 +1,8 @@ # Databases: -GitLab use mysql as default database but you are free to use PostgreSQL or SQLite. +GitLab use MySQL as default database but you are free to use PostgreSQL. -## SQLite - - sudo apt-get install -y sqlite3 libsqlite3-dev - ## MySQL sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev @@ -47,9 +43,6 @@ GitLab use mysql as default database but you are free to use PostgreSQL or SQLit #### Select the database you want to use - # SQLite - sudo -u gitlab cp config/database.yml.sqlite config/database.yml - # Mysql sudo -u gitlab cp config/database.yml.mysql config/database.yml @@ -61,11 +54,7 @@ GitLab use mysql as default database but you are free to use PostgreSQL or SQLit #### Install gems # mysql - sudo -u gitlab -H bundle install --without development test sqlite postgres --deployment + sudo -u gitlab -H bundle install --without development test postgres --deployment # or postgres - sudo -u gitlab -H bundle install --without development test sqlite mysql --deployment - - # or sqlite - sudo -u gitlab -H bundle install --without development test mysql postgres --deployment - + sudo -u gitlab -H bundle install --without development test mysql --deployment diff --git a/doc/install/installation.md b/doc/install/installation.md index 07ed0b0f9de..6876a8756b1 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -178,7 +178,7 @@ and ensure you have followed all of the above steps carefully. sudo gem install charlock_holmes --version '0.6.9' sudo gem install bundler - sudo -u gitlab -H bundle install --without development test sqlite postgres --deployment + sudo -u gitlab -H bundle install --without development test postgres --deployment #### Configure git client @@ -269,7 +269,7 @@ You can login via web using admin generated with setup: # Advanced setup tips: -_Checkout databases.md for postgres or sqlite_ +_Checkout databases.md for PostgreSQL_ ## Customizing Resque's Redis connection diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature index fce85ce9901..226d3d5d5b5 100644 --- a/features/admin/active_tab.feature +++ b/features/admin/active_tab.feature @@ -12,6 +12,11 @@ Feature: Admin active tab Then the active main tab should be Projects And no other main tabs should be active + Scenario: On Admin Groups + Given I visit admin groups page + Then the active main tab should be Groups + And no other main tabs should be active + Scenario: On Admin Users Given I visit admin users page Then the active main tab should be Users diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index 24296f46579..972f8e36609 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -15,6 +15,12 @@ Feature: Dashboard And I visit dashboard page Then I should see groups list + Scenario: I should see correct projects count + Given I have group with projects + And group has a projects that does not belongs to me + When I visit dashboard page + Then I should see 1 project at group list + Scenario: I should see last push widget Then I should see last push widget And I click "Create Merge Request" link diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 596e8bd7d41..99529373d4d 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -57,13 +57,14 @@ Feature: Project Issues Then I should see "Release 0.3" in issues And I should not see "Release 0.4" in issues - @javascript - Scenario: I clear search - Given I click link "All" - And I fill in issue search with "Something" - And I fill in issue search with "" - Then I should see "Release 0.4" in issues - And I should see "Release 0.3" in issues + # TODO: find out solution for poltergeist/phantomjs or remove + # @javascript + # Scenario: I clear search + # Given I click link "All" + # And I fill in issue search with "Something" + # And I fill in issue search with "" + # Then I should see "Release 0.4" in issues + # And I should see "Release 0.3" in issues @javascript Scenario: I create Issue with pre-selected milestone diff --git a/features/project/wiki.feature b/features/project/wiki.feature index 51370565a3b..f052e2f244c 100644 --- a/features/project/wiki.feature +++ b/features/project/wiki.feature @@ -7,9 +7,3 @@ Feature: Project Wiki Scenario: Add new page Given I create Wiki page Then I should see newly created wiki page - - @javascript - Scenario: I comment wiki page - Given I create Wiki page - And I leave a comment like "XML attached" - Then I should see comment "XML attached" diff --git a/features/steps/admin/admin_active_tab.rb b/features/steps/admin/admin_active_tab.rb index 29290892823..05a9a686e01 100644 --- a/features/steps/admin/admin_active_tab.rb +++ b/features/steps/admin/admin_active_tab.rb @@ -11,6 +11,10 @@ class AdminActiveTab < Spinach::FeatureSteps ensure_active_main_tab('Projects') end + Then 'the active main tab should be Groups' do + ensure_active_main_tab('Groups') + end + Then 'the active main tab should be Users' do ensure_active_main_tab('Users') end diff --git a/features/steps/admin/admin_groups.rb b/features/steps/admin/admin_groups.rb index e1759013ce7..5386f473320 100644 --- a/features/steps/admin/admin_groups.rb +++ b/features/steps/admin/admin_groups.rb @@ -9,8 +9,7 @@ class AdminGroups < Spinach::FeatureSteps And 'submit form with new group info' do fill_in 'group_name', :with => 'gitlab' - fill_in 'group_code', :with => 'gitlab' - click_button "Save group" + click_button "Create group" end Then 'I should see newly created group' do diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index 99c48738876..775a721f1a4 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -103,4 +103,14 @@ class Dashboard < Spinach::FeatureSteps page.should have_link group.name end end + + And 'group has a projects that does not belongs to me' do + @forbidden_project1 = create(:project, group: @group) + @forbidden_project2 = create(:project, group: @group) + end + + Then 'I should see 1 project at group list' do + page.find('span.last_activity/span').should have_content('1') + end + end diff --git a/features/steps/dashboard/dashboard_issues.rb b/features/steps/dashboard/dashboard_issues.rb index e5caf905f95..5ace88023f0 100644 --- a/features/steps/dashboard/dashboard_issues.rb +++ b/features/steps/dashboard/dashboard_issues.rb @@ -7,6 +7,7 @@ class DashboardIssues < Spinach::FeatureSteps issues.each do |issue| page.should have_content(issue.title[0..10]) page.should have_content(issue.project.name) + page.should have_link(issue.project.name) end end diff --git a/features/steps/project/create_project.rb b/features/steps/project/create_project.rb index 6d2ca3f9b56..b9b4534ed68 100644 --- a/features/steps/project/create_project.rb +++ b/features/steps/project/create_project.rb @@ -4,8 +4,6 @@ class CreateProject < Spinach::FeatureSteps And 'fill project form with valid data' do fill_in 'project_name', :with => 'NewProject' - fill_in 'project_code', :with => 'NPR' - fill_in 'project_path', :with => 'newproject' click_button "Create project" end diff --git a/features/steps/project/project_issues.rb b/features/steps/project/project_issues.rb index 88bfac638d0..cc0acb5b481 100644 --- a/features/steps/project/project_issues.rb +++ b/features/steps/project/project_issues.rb @@ -73,7 +73,6 @@ class ProjectIssues < Spinach::FeatureSteps end And 'I fill in issue search with ""' do - page.execute_script("$('.issue_search').val('').keyup();"); fill_in 'issue_search', with: "" end diff --git a/features/support/env.rb b/features/support/env.rb index 1a72d765197..a30b357718e 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -32,6 +32,11 @@ end DatabaseCleaner.strategy = :truncation Spinach.hooks.before_scenario do + # Use tmp dir for FS manipulations + Gitlab.config.stub(git_base_path: Rails.root.join('tmp', 'test-git-base-path')) + FileUtils.rm_rf Gitlab.config.git_base_path + FileUtils.mkdir_p Gitlab.config.git_base_path + DatabaseCleaner.start end diff --git a/lib/api.rb b/lib/api.rb index 7a1845443e7..99e2074f306 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -19,5 +19,6 @@ module Gitlab mount Milestones mount Session mount MergeRequests + mount Notes end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9e605a607a2..f985636aa10 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -70,8 +70,15 @@ module Gitlab end class Note < Grape::Entity + expose :id + expose :note, as: :body expose :author, using: Entities::UserBasic + expose :created_at + end + + class MRNote < Grape::Entity expose :note + expose :author, using: Entities::UserBasic end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index a339ec4a6fc..e9305b40836 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -6,7 +6,7 @@ module Gitlab def user_project if @project ||= current_user.projects.find_by_id(params[:id]) || - current_user.projects.find_by_code(params[:id]) + current_user.projects.find_by_path(params[:id]) else not_found! end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d8f2c51293a..1fa0c549b13 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -4,9 +4,9 @@ module Gitlab before { authenticate! } resource :projects do - + # List merge requests - # + # # Parameters: # id (required) - The ID or code name of a project # @@ -15,24 +15,24 @@ module Gitlab # get ":id/merge_requests" do authorize! :read_merge_request, user_project - + present paginate(user_project.merge_requests), with: Entities::MergeRequest end - + # Show MR - # + # # Parameters: # id (required) - The ID or code name of a project # merge_request_id (required) - The ID of MR - # + # # Example: # GET /projects/:id/merge_request/:merge_request_id # get ":id/merge_request/:merge_request_id" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - + authorize! :read_merge_request, merge_request - + present merge_request, with: Entities::MergeRequest end @@ -45,17 +45,17 @@ module Gitlab # target_branch (required) - The target branch # assignee_id - Assignee user ID # title (required) - Title of MR - # + # # Example: # POST /projects/:id/merge_requests # post ":id/merge_requests" do authorize! :write_merge_request, user_project - + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] merge_request = user_project.merge_requests.new(attrs) merge_request.author = current_user - + if merge_request.save merge_request.reload_code present merge_request, with: Entities::MergeRequest @@ -80,9 +80,9 @@ module Gitlab put ":id/merge_request/:merge_request_id" do attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed] merge_request = user_project.merge_requests.find(params[:merge_request_id]) - + authorize! :modify_merge_request, merge_request - + if merge_request.update_attributes attrs merge_request.reload_code merge_request.mark_as_unchecked @@ -98,7 +98,7 @@ module Gitlab # id (required) - The ID or code name of a project # merge_request_id (required) - ID of MR # note (required) - Text of comment - # Examples: + # Examples: # POST /projects/:id/merge_request/:merge_request_id/comments # post ":id/merge_request/:merge_request_id/comments" do @@ -107,7 +107,7 @@ module Gitlab note.author = current_user if note.save - present note, with: Entities::Note + present note, with: Entities::MRNote else not_found! end diff --git a/lib/api/notes.rb b/lib/api/notes.rb new file mode 100644 index 00000000000..b47ff5c300f --- /dev/null +++ b/lib/api/notes.rb @@ -0,0 +1,94 @@ +module Gitlab + # Notes API + class Notes < Grape::API + before { authenticate! } + + NOTEABLE_TYPES = [Issue, Snippet] + + resource :projects do + # Get a list of project wall notes + # + # Parameters: + # id (required) - The ID or code name of a project + # Example Request: + # GET /projects/:id/notes + get ":id/notes" do + @notes = user_project.common_notes + present paginate(@notes), with: Entities::Note + end + + # Create a new project wall note + # + # Parameters: + # id (required) - The ID or code name of a project + # body (required) - The content of a note + # Example Request: + # POST /projects/:id/notes + post ":id/notes" do + @note = user_project.notes.new(note: params[:body]) + @note.author = current_user + + if @note.save + present @note, with: Entities::Note + else + not_found! + end + end + + NOTEABLE_TYPES.each do |noteable_type| + noteables_str = noteable_type.to_s.underscore.pluralize + noteable_id_str = "#{noteable_type.to_s.underscore}_id" + + # Get a list of project +noteable+ notes + # + # Parameters: + # id (required) - The ID or code name of a project + # noteable_id (required) - The ID of an issue or snippet + # Example Request: + # GET /projects/:id/issues/:noteable_id/notes + # GET /projects/:id/snippets/:noteable_id/notes + get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) + present paginate(@noteable.notes), with: Entities::Note + end + + # Get a single +noteable+ note + # + # Parameters: + # id (required) - The ID or code name of a project + # noteable_id (required) - The ID of an issue or snippet + # note_id (required) - The ID of a note + # Example Request: + # GET /projects/:id/issues/:noteable_id/notes/:note_id + # GET /projects/:id/snippets/:noteable_id/notes/:note_id + get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) + @note = @noteable.notes.find(params[:note_id]) + present @note, with: Entities::Note + end + + # Create a new +noteable+ note + # + # Parameters: + # id (required) - The ID or code name of a project + # noteable_id (required) - The ID of an issue or snippet + # body (required) - The content of a note + # Example Request: + # POST /projects/:id/issues/:noteable_id/notes + # POST /projects/:id/snippets/:noteable_id/notes + post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) + @note = @noteable.notes.new(note: params[:body]) + @note.author = current_user + @note.project = user_project + + if @note.save + present @note, with: Entities::Note + else + not_found! + end + end + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ac20bbeccab..384dbcd5473 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -38,11 +38,7 @@ module Gitlab # Example Request # POST /projects post do - params[:code] ||= params[:name] - params[:path] ||= params[:name] - attrs = attributes_for_keys [:code, - :path, - :name, + attrs = attributes_for_keys [:name, :description, :default_branch, :issues_enabled, diff --git a/lib/api/users.rb b/lib/api/users.rb index 57e0aa108cf..cad99fd9f7b 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -38,7 +38,7 @@ module Gitlab # POST /users post do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username] user = User.new attrs, as: :admin if user.save present user, with: Entities::User diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 5a24c5d01b5..056fb034daf 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -34,6 +34,7 @@ module Gitlab extern_uid: uid, provider: provider, name: name, + username: email.match(/^[^@]*/)[0], email: email, password: password, password_confirmation: password, diff --git a/lib/gitlab/backend/gitolite.rb b/lib/gitlab/backend/gitolite.rb index fe5dcef40a9..7c3861bdd13 100644 --- a/lib/gitlab/backend/gitolite.rb +++ b/lib/gitlab/backend/gitolite.rb @@ -23,7 +23,14 @@ module Gitlab end def update_repository project - config.update_project!(project.path, project) + config.update_project!(project) + end + + def move_repository(old_repo, project) + config.apply do |config| + config.clean_repo(old_repo) + config.update_project(project) + end end def remove_repository project @@ -38,6 +45,12 @@ module Gitlab config.admin_all_repo! end + def update_repositories projects + config.apply do |config| + config.update_projects(projects) + end + end + alias_method :create_repository, :update_repository end end diff --git a/lib/gitlab/backend/gitolite_config.rb b/lib/gitlab/backend/gitolite_config.rb index 7ae34de66bc..70ccc4782c6 100644 --- a/lib/gitlab/backend/gitolite_config.rb +++ b/lib/gitlab/backend/gitolite_config.rb @@ -83,7 +83,11 @@ module Gitlab def destroy_project(project) FileUtils.rm_rf(project.path_to_repo) - conf.rm_repo(project.path) + conf.rm_repo(project.path_with_namespace) + end + + def clean_repo repo_name + conf.rm_repo(repo_name) end def destroy_project!(project) @@ -105,18 +109,18 @@ module Gitlab end # update or create - def update_project(repo_name, project) + def update_project(project) repo = update_project_config(project, conf) conf.add_repo(repo, true) end - def update_project!(repo_name, project) + def update_project!( project) apply do |config| - config.update_project(repo_name, project) + config.update_project(project) end end - # Updates many projects and uses project.path as the repo path + # Updates many projects and uses project.path_with_namespace as the repo path # An order of magnitude faster than update_project def update_projects(projects) projects.each do |project| @@ -126,7 +130,7 @@ module Gitlab end def update_project_config(project, conf) - repo_name = project.path + repo_name = project.path_with_namespace repo = if conf.has_repo?(repo_name) conf.get_repo(repo_name) diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index dd5a9becafc..9fafc9617d1 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -4,10 +4,14 @@ module Grack def valid? # Authentication with username and password - email, password = @auth.credentials - self.user = User.find_by_email(email) + login, password = @auth.credentials + + self.user = User.find_by_email(login) || User.find_by_username(login) + return false unless user.try(:valid_password?, password) + email = user.email + # Set GL_USER env variable ENV['GL_USER'] = email # Pass Gitolite update hook @@ -18,8 +22,8 @@ module Grack @env['SCRIPT_NAME'] = "" # Find project by PATH_INFO from env - if m = /^\/([\w\.-]+)\.git/.match(@request.path_info).to_a - self.project = Project.find_by_path(m.last) + if m = /^\/([\w\.\/-]+)\.git/.match(@request.path_info).to_a + self.project = Project.find_with_namespace(m.last) return false unless project end diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index cf9a4c4afa2..8b4eee5da06 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -11,7 +11,7 @@ module Gitlab def self.read_latest path = Rails.root.join("log", file_name) self.build unless File.exist?(path) - logs = File.read(path).split("\n") + logs = `tail -n 2000 #{path}`.split("\n") end def self.build diff --git a/lib/gitlab/project_mover.rb b/lib/gitlab/project_mover.rb new file mode 100644 index 00000000000..eeab22ae6e7 --- /dev/null +++ b/lib/gitlab/project_mover.rb @@ -0,0 +1,44 @@ +# ProjectMover class +# +# Used for moving project repositories from one subdir to another +module Gitlab + class ProjectMover + class ProjectMoveError < StandardError; end + + attr_reader :project, :old_dir, :new_dir + + def initialize(project, old_dir, new_dir) + @project = project + @old_dir = old_dir + @new_dir = new_dir + end + + def execute + # Create new dir if missing + new_dir_path = File.join(Gitlab.config.git_base_path, new_dir) + system("mkdir -m 770 #{new_dir_path}") unless File.exists?(new_dir_path) + + old_path = File.join(Gitlab.config.git_base_path, old_dir, "#{project.path}.git") + new_path = File.join(new_dir_path, "#{project.path}.git") + + if File.exists? new_path + raise ProjectMoveError.new("Destination #{new_path} already exists") + end + + if system("mv #{old_path} #{new_path}") + log_info "Project #{project.name} was moved from #{old_path} to #{new_path}" + true + else + message = "Project #{project.name} cannot be moved from #{old_path} to #{new_path}" + log_info "Error! #{message}" + raise ProjectMoveError.new(message) + end + end + + protected + + def log_info message + Gitlab::AppLogger.info message + end + end +end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb new file mode 100644 index 00000000000..a3f38b1c360 --- /dev/null +++ b/lib/gitlab/regex.rb @@ -0,0 +1,19 @@ +module Gitlab + module Regex + extend self + + def username_regex + default_regex + end + + def path_regex + default_regex + end + + protected + + def default_regex + /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/ + end + end +end diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index 28b6f538d00..91c83d81029 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -41,11 +41,11 @@ module Gitlab end def lock_file - Rails.root.join("tmp", "#{project.path}.lock") + Rails.root.join("tmp", "satellite_#{project.id}.lock") end def path - Rails.root.join("tmp", "repo_satellites", project.path) + Rails.root.join("tmp", "repo_satellites", project.path_with_namespace) end def repo diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index 48b4da9d339..bd590f92734 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -12,10 +12,12 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML def block_code(code, language) options = { options: {encoding: 'utf-8'} } - if Pygments::Lexer.find(language) - Pygments.highlight(code, options.merge(lexer: language.downcase)) - else - Pygments.highlight(code, options) + h.content_tag :div, class: h.user_color_scheme_class do + if Pygments::Lexer.find(language) + Pygments.highlight(code, options.merge(lexer: language.downcase)) + else + Pygments.highlight(code, options) + end.html_safe end end diff --git a/lib/tasks/gitlab/activate_namespaces.rake b/lib/tasks/gitlab/activate_namespaces.rake new file mode 100644 index 00000000000..08df0a8040b --- /dev/null +++ b/lib/tasks/gitlab/activate_namespaces.rake @@ -0,0 +1,67 @@ +namespace :gitlab do + desc "GITLAB | Enable usernames and namespaces for user projects" + task activate_namespaces: :environment do + print "\nUsernames for users:".yellow + + User.find_each(batch_size: 500) do |user| + next if user.namespace + + User.transaction do + username = user.email.match(/^[^@]*/)[0] + if user.update_attributes!(username: username) + print '.'.green + else + print 'F'.red + end + end + end + + print "\n\nDirs for groups:".yellow + + Group.find_each(batch_size: 500) do |group| + if group.ensure_dir_exist + print '.'.green + else + print 'F'.red + end + end + + print "\n\nMove projects from groups under groups dirs:".yellow + git_path = Gitlab.config.git_base_path + + Project.where('namespace_id IS NOT NULL').find_each(batch_size: 500) do |project| + next unless project.group + + group = project.group + + puts "\n" + print " * #{project.name}: " + + new_path = File.join(git_path, project.path_with_namespace + '.git') + + if File.exists?(new_path) + print "ok. already at #{new_path}".cyan + next + end + + old_path = File.join(git_path, project.path + '.git') + + unless File.exists?(old_path) + print "missing. not found at #{old_path}".red + next + end + + begin + Gitlab::ProjectMover.new(project, '', group.path).execute + print "ok. Moved to #{new_path}".green + rescue + print "Failed moving to #{new_path}".red + end + end + + print "\n\nRebuild gitolite:".yellow + gitolite = Gitlab::Gitolite.new + gitolite.update_repositories(Project.where('namespace_id IS NOT NULL')) + puts "\n" + end +end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 17a0e336bb5..c01fe479dba 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -82,7 +82,7 @@ namespace :gitlab do end settings = YAML.load_file("backup_information.yml") - ENV["VERSION"] = "#{settings["db_version"]}" if settings["db_version"].to_i > 0 + ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0 # restoring mismatching backups can lead to unexpected problems if settings[:gitlab_version] != %x{git rev-parse HEAD}.gsub(/\n/,"") diff --git a/lib/tasks/resque.rake b/lib/tasks/resque.rake index 9b30bb0a292..e6987e17639 100644 --- a/lib/tasks/resque.rake +++ b/lib/tasks/resque.rake @@ -1 +1,14 @@ require 'resque/tasks' + +# Fix Exception +# ActiveRecord::StatementInvalid +# Error +# PGError: ERROR: prepared statement "a3" already exists +task "resque:setup" => :environment do + Resque.after_fork do |job| + ActiveRecord::Base.establish_connection + end +end + +desc "Alias for resque:work (To run workers on Heroku)" +task "jobs:work" => "resque:work" diff --git a/public/404.html b/public/404.html index 3e56e52cc18..867f193a98f 100644 --- a/public/404.html +++ b/public/404.html @@ -7,9 +7,8 @@ <body> <h1>404</h1> - <div> - <h2>The page you were looking for doesn't exist.</h2> - <p>You may have mistyped the address or the page may have moved.</p> - </div> + <h3>The page you were looking for doesn't exist.</h3> + <hr/> + <p>You may have mistyped the address or the page may have moved.</p> </body> </html> diff --git a/public/500.html b/public/500.html index 3be1cc259c0..5b78e3e38cb 100644 --- a/public/500.html +++ b/public/500.html @@ -4,13 +4,10 @@ <title>We're sorry, but something went wrong (500)</title> <link href="/static.css" media="screen" rel="stylesheet" type="text/css" /> </head> - <body> - <!-- This file lives in public/500.html --> <h1>500</h1> - <div> - <h2>We're sorry, but something went wrong.</h2> - <p>We've been notified about this issue and we'll take a look at it shortly.</p> - </div> + <h3>We're sorry, but something went wrong.</h3> + <hr/> + <p>We've been notified about this issue and we'll take a look at it shortly.</p> </body> </html> diff --git a/public/githost_error.html b/public/githost_error.html deleted file mode 100644 index b5258ce160e..00000000000 --- a/public/githost_error.html +++ /dev/null @@ -1,36 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>We're sorry, but we can't get access to your gitolite</title> - <style type="text/css"> - body { background-color: #EAEAEA; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 600px; - padding: 0 4em; - margin: 4em auto 0 auto; - } - h1 { font-size: 48px; color: #444; line-height: 1.5em; } - h2 { font-size: 24px; color: #666; line-height: 1.5em; } - h3, code { text-align:left; } - code pre { margin-left:40px; } - </style> -</head> - -<body> - <!-- This file lives in public/500.html --> - <div class="dialog"> - <h1>Gitolite Error</h1> - <h2>Application can't get access to your gitolite system.</h2> - <hr> - <h3> 1. Check 'config/gitlab.yml' for correct settings.</h3> - <h3> 2. Make sure web server user has access to gitolite. <a href="https://github.com/gitlabhq/gitlabhq/wiki/Gitolite">Setup tutorial</a></h3> - <h3> 3. Try: </h3> - <code> - <pre> -sudo chmod -R 770 /home/git/repositories/ -sudo chown -R git:git /home/git/repositories/ - </pre> - </code> - </div> -</body> -</html> diff --git a/public/static.css b/public/static.css index 6090d7b2abb..aa834553a1c 100644 --- a/public/static.css +++ b/public/static.css @@ -1,57 +1,31 @@ -body { color: #666; text-align: center; font-family: arial, sans-serif; margin:0; padding:0; } -h1 { font-size: 48px; color: #444; line-height: 1.5em; } +body { + color: #666; + text-align: center; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + sans-serif; + margin:0; + width: 800px; + margin: auto; + font-size: 14px; +} +h1 { + font-size: 56px; + line-height: 100px; + font-weight: normal; + color: #456; +} h2 { font-size: 24px; color: #666; line-height: 1.5em; } -.alert-message { - position: relative; - padding: 7px 15px; - margin-bottom: 18px; - color: #404040; - background-color: #eedc94; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94)); - background-image: -moz-linear-gradient(top, #fceec1, #eedc94); - background-image: -ms-linear-gradient(top, #fceec1, #eedc94); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94)); - background-image: -webkit-linear-gradient(top, #fceec1, #eedc94); - background-image: -o-linear-gradient(top, #fceec1, #eedc94); - background-image: linear-gradient(top, #fceec1, #eedc94); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFCEEC1', endColorstr='#FFEEDC94', GradientType=0); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border-color: #eedc94 #eedc94 #e4c652; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) fadein(rgba(0, 0, 0, 0.1), 15%); - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - border-width: 1px; - border-style: solid; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); -} -.alert-message .close { - margin-top: 1px; - *margin-top: 0; -} -.alert-message a { - font-weight: bold; - color: #404040; -} -.alert-message.danger p a, .alert-message.error p a, .alert-message.success p a, .alert-message.info p a { - color: #404040; -} -.alert-message h5 { - line-height: 18px; -} -.alert-message p { - margin-bottom: 0; -} -.alert-message div { - margin-top: 5px; - margin-bottom: 2px; +h3 { + color: #456; + font-size: 20px; + font-weight: normal; line-height: 28px; } -.alert-message.block-message.error { - background: #FDDFDE; - border-color: #FBC7C6; +hr { + margin: 18px 0; + border: 0; + border-top: 1px solid #EEE; + border-bottom: 1px solid white; } - diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb new file mode 100644 index 00000000000..5aef4c676ee --- /dev/null +++ b/spec/controllers/commit_controller_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe CommitController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:commit) { project.last_commit_for("master") } + + before do + sign_in(user) + + project.add_access(user, :read, :admin) + end + + describe "#show" do + shared_examples "export as" do |format| + it "should generally work" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response).to be_success + end + + it "should generate it" do + Commit.any_instance.should_receive(:"to_#{format}") + + get :show, project_id: project.code, id: commit.id, format: format + end + + it "should render it" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to eq(commit.send(:"to_#{format}")) + end + + it "should not escape Html" do + Commit.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ') + + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to_not include('&') + expect(response.body).to_not include('>') + expect(response.body).to_not include('<') + expect(response.body).to_not include('"') + end + end + + describe "as diff" do + include_examples "export as", :diff + let(:format) { :diff } + + it "should really only be a git diff" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to start_with("diff --git") + end + end + + describe "as patch" do + include_examples "export as", :patch + let(:format) { :patch } + + it "should really be a git email patch" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to start_with("From #{commit.id}") + end + + it "should contain a git diff" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to match(/^diff --git/) + end + end + end +end diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/commits_controller_spec.rb index bf335634da9..da33fd8a2b5 100644 --- a/spec/controllers/commits_controller_spec.rb +++ b/spec/controllers/commits_controller_spec.rb @@ -13,7 +13,7 @@ describe CommitsController do describe "GET show" do context "as atom feed" do it "should render as atom" do - get :show, project_id: project.code, id: "master.atom" + get :show, project_id: project.path, id: "master.atom" response.should be_success response.content_type.should == 'application/atom+xml' end diff --git a/spec/controllers/merge_requests_controller_spec.rb b/spec/controllers/merge_requests_controller_spec.rb new file mode 100644 index 00000000000..7aebe06cf0c --- /dev/null +++ b/spec/controllers/merge_requests_controller_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe MergeRequestsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request_with_diffs, project: project) } + + before do + sign_in(user) + project.add_access(user, :read, :admin) + MergeRequestsController.any_instance.stub(validates_merge_request: true) + end + + describe "#show" do + shared_examples "export as" do |format| + it "should generally work" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response).to be_success + end + + it "should generate it" do + MergeRequest.any_instance.should_receive(:"to_#{format}") + + get :show, project_id: project.code, id: merge_request.id, format: format + end + + it "should render it" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body).to eq(merge_request.send(:"to_#{format}")) + end + + it "should not escape Html" do + MergeRequest.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ') + + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body).to_not include('&') + expect(response.body).to_not include('>') + expect(response.body).to_not include('<') + expect(response.body).to_not include('"') + end + end + + describe "as diff" do + include_examples "export as", :diff + let(:format) { :diff } + + it "should really only be a git diff" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body).to start_with("diff --git") + end + end + + describe "as patch" do + include_examples "export as", :patch + let(:format) { :patch } + + it "should really be a git email patch with commit" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}") + end + + # TODO: fix or remove + #it "should contain as many patches as there are commits" do + #get :show, project_id: project.code, id: merge_request.id, format: format + + #patch_count = merge_request.commits.count + #merge_request.commits.each_with_index do |commit, patch_num| + #expect(response.body).to match(/^From #{commit.id}/) + #expect(response.body).to match(/^Subject: \[PATCH #{patch_num}\/#{patch_count}\]/) + #end + #end + + it "should contain git diffs" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body).to match(/^diff --git/) + end + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 7c33f0ecc8b..a26a77dd860 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -12,6 +12,7 @@ FactoryGirl.define do factory :user, aliases: [:author, :assignee, :owner] do email { Faker::Internet.email } name + username { Faker::Internet.user_name } password "123456" password_confirmation { password } @@ -25,13 +26,19 @@ FactoryGirl.define do factory :project do sequence(:name) { |n| "project#{n}" } path { name.downcase.gsub(/\s/, '_') } - code { name.downcase.gsub(/\s/, '_') } owner end factory :group do sequence(:name) { |n| "group#{n}" } - code { name.downcase.gsub(/\s/, '_') } + path { name.downcase.gsub(/\s/, '_') } + owner + type 'Group' + end + + factory :namespace do + sequence(:name) { |n| "group#{n}" } + path { name.downcase.gsub(/\s/, '_') } owner end @@ -63,7 +70,22 @@ FactoryGirl.define do closed true end + # pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d) + trait :with_diffs do + target_branch "bcf03b5d~3" + source_branch "bcf03b5d" + st_commits do + [Commit.new(project.repo.commit('bcf03b5d')), + Commit.new(project.repo.commit('bcf03b5d~1')), + Commit.new(project.repo.commit('bcf03b5d~2'))] + end + st_diffs do + project.repo.diff("bcf03b5d~3", "bcf03b5d") + end + end + factory :closed_merge_request, traits: [:closed] + factory :merge_request_with_diffs, traits: [:with_diffs] end factory :note do diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index ec830e40ecd..05e4527b278 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -329,9 +329,11 @@ describe GitlabMarkdownHelper do end it "should leave code blocks untouched" do - markdown("\n some code from $#{snippet.id}\n here too\n").should == "<div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div>" + helper.stub(:user_color_scheme_class).and_return(:white) - markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should == "<div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div>" + helper.markdown("\n some code from $#{snippet.id}\n here too\n").should == "<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div></div>" + + helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should == "<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div></div>" end it "should leave inline code untouched" do diff --git a/spec/lib/project_mover_spec.rb b/spec/lib/project_mover_spec.rb new file mode 100644 index 00000000000..af24635d82b --- /dev/null +++ b/spec/lib/project_mover_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe Gitlab::ProjectMover do + let(:base_path) { Rails.root.join('tmp', 'rspec-sandbox') } + + before do + FileUtils.rm_rf base_path if File.exists? base_path + + Gitlab.config.stub(git_base_path: base_path) + + @project = create(:project) + end + + after do + FileUtils.rm_rf base_path + end + + it "should move project to subdir" do + mk_dir base_path, '', @project.path + mover = Gitlab::ProjectMover.new(@project, '', 'opensource') + + mover.execute.should be_true + moved?('opensource', @project.path).should be_true + end + + it "should move project from one subdir to another" do + mk_dir base_path, 'vsizov', @project.path + mover = Gitlab::ProjectMover.new(@project, 'vsizov', 'randx') + + mover.execute.should be_true + moved?('randx', @project.path).should be_true + end + + it "should move project from subdir to base" do + mk_dir base_path, 'vsizov', @project.path + mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') + + mover.execute.should be_true + moved?('', @project.path).should be_true + end + + it "should raise if destination exists" do + mk_dir base_path, '', @project.path + mk_dir base_path, 'vsizov', @project.path + mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') + + expect { mover.execute }.to raise_error(Gitlab::ProjectMover::ProjectMoveError) + end + + it "should raise if move failed" do + mk_dir base_path + mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') + + expect { mover.execute }.to raise_error(Gitlab::ProjectMover::ProjectMoveError) + end + + + def mk_dir base_path, namespace = '', project_path = '' + FileUtils.mkdir_p File.join(base_path, namespace, project_path + ".git") + end + + def moved? namespace, path + File.exists?(File.join(base_path, namespace, path + '.git')) + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index b6b1769fc80..58698eec9f4 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -169,9 +169,7 @@ describe Notify do end describe 'project access changed' do - let(:project) { create(:project, - path: "Fuu", - code: "Fuu") } + let(:project) { create(:project) } let(:user) { create(:user) } let(:users_project) { create(:users_project, project: project, diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 6ae2cb20169..108bc303540 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1,13 +1,14 @@ # == Schema Information # -# Table name: groups +# Table name: namespaces # # id :integer not null, primary key # name :string(255) not null -# code :string(255) not null +# path :string(255) not null # owner_id :integer not null # created_at :datetime not null # updated_at :datetime not null +# type :string(255) # require 'spec_helper' @@ -18,7 +19,15 @@ describe Group do it { should have_many :projects } it { should validate_presence_of :name } it { should validate_uniqueness_of(:name) } - it { should validate_presence_of :code } - it { should validate_uniqueness_of(:code) } + it { should validate_presence_of :path } + it { should validate_uniqueness_of(:path) } it { should validate_presence_of :owner } + + describe :users do + it { group.users.should == [group.owner] } + end + + describe :human_name do + it { group.human_name.should == group.name } + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb new file mode 100644 index 00000000000..d0de4a7b7fb --- /dev/null +++ b/spec/models/namespace_spec.rb @@ -0,0 +1,78 @@ +# == Schema Information +# +# Table name: namespaces +# +# id :integer not null, primary key +# name :string(255) not null +# path :string(255) not null +# owner_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) +# + +require 'spec_helper' + +describe Namespace do + let!(:namespace) { create(:namespace) } + + it { should have_many :projects } + it { should validate_presence_of :name } + it { should validate_uniqueness_of(:name) } + it { should validate_presence_of :path } + it { should validate_uniqueness_of(:path) } + it { should validate_presence_of :owner } + + describe "Mass assignment" do + it { should allow_mass_assignment_of(:name) } + it { should allow_mass_assignment_of(:path) } + end + + describe "Respond to" do + it { should respond_to(:human_name) } + it { should respond_to(:to_param) } + end + + it { Namespace.global_id.should == 'GLN' } + + describe :to_param do + it { namespace.to_param.should == namespace.path } + end + + describe :human_name do + it { namespace.human_name.should == namespace.owner_name } + end + + describe :search do + before do + @namespace = create :namespace + end + + it { Namespace.search(@namespace.path).should == [@namespace] } + it { Namespace.search('unknown').should == [] } + end + + describe :move_dir do + before do + @namespace = create :namespace + @namespace.stub(path_changed?: true) + end + + it "should raise error when dirtory exists" do + expect { @namespace.move_dir }.to raise_error("Already exists") + end + + it "should move dir if path changed" do + new_path = @namespace.path + "_new" + @namespace.stub(path_was: @namespace.path) + @namespace.stub(path: new_path) + @namespace.move_dir.should be_true + end + end + + describe :rm_dir do + it "should remove dir" do + namespace.rm_dir.should be_true + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5bcab924496..db0d30727b4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -9,14 +9,13 @@ # created_at :datetime not null # updated_at :datetime not null # private_flag :boolean default(TRUE), not null -# code :string(255) # owner_id :integer # default_branch :string(255) # issues_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null -# group_id :integer +# namespace_id :integer # require 'spec_helper' @@ -24,6 +23,7 @@ require 'spec_helper' describe Project do describe "Associations" do it { should belong_to(:group) } + it { should belong_to(:namespace) } it { should belong_to(:owner).class_name('User') } it { should have_many(:users) } it { should have_many(:events).dependent(:destroy) } @@ -40,6 +40,7 @@ describe Project do end describe "Mass assignment" do + it { should_not allow_mass_assignment_of(:namespace_id) } it { should_not allow_mass_assignment_of(:owner_id) } it { should_not allow_mass_assignment_of(:private_flag) } end @@ -58,9 +59,6 @@ describe Project do it { should ensure_length_of(:description).is_within(0..2000) } - it { should validate_presence_of(:code) } - it { should validate_uniqueness_of(:code) } - it { should ensure_length_of(:code).is_within(1..255) } # TODO: Formats it { should validate_presence_of(:owner) } @@ -151,7 +149,7 @@ describe Project do end it "returns the full web URL for this repo" do - project = Project.new(code: "somewhere") + project = Project.new(path: "somewhere") project.web_url.should == "#{Gitlab.config.url}/somewhere" end @@ -162,7 +160,7 @@ describe Project do end it "should be invalid repo" do - project = Project.new(name: "ok_name", path: "/INVALID_PATH/", code: "NEOK") + project = Project.new(name: "ok_name", path: "/INVALID_PATH/", path: "NEOK") project.valid_repo?.should be_false end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4ac699b1c45..279e315b693 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -30,14 +30,17 @@ # locked_at :datetime # extern_uid :string(255) # provider :string(255) +# username :string(255) # require 'spec_helper' describe User do describe "Associations" do + it { should have_one(:namespace) } it { should have_many(:users_projects).dependent(:destroy) } it { should have_many(:projects) } + it { should have_many(:groups) } it { should have_many(:my_own_projects).class_name('Project') } it { should have_many(:keys).dependent(:destroy) } it { should have_many(:events).class_name('Event').dependent(:destroy) } @@ -55,6 +58,7 @@ describe User do end describe 'validations' do + it { should validate_presence_of(:username) } it { should validate_presence_of(:projects_limit) } it { should validate_numericality_of(:projects_limit) } it { should allow_value(0).for(:projects_limit) } diff --git a/spec/observers/user_observer_spec.rb b/spec/observers/user_observer_spec.rb index 08254f44026..4ba0f05df5d 100644 --- a/spec/observers/user_observer_spec.rb +++ b/spec/observers/user_observer_spec.rb @@ -13,7 +13,12 @@ describe UserObserver do end context 'when a new user is created' do - let(:user) { double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local') } + let(:user) { double(:user, id: 42, + password: 'P@ssword!', + name: 'John', + email: 'u@mail.local', + username: 'root', + create_namespace: true) } let(:notification) { double :notification } it 'sends an email' do diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb index cbe42248033..548f1893848 100644 --- a/spec/observers/users_project_observer_spec.rb +++ b/spec/observers/users_project_observer_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' describe UsersProjectObserver do let(:user) { create(:user) } - let(:project) { create(:project, - code: "Fuu", - path: "Fuu" ) } + let(:project) { create(:project) } let(:users_project) { create(:users_project, project: project, user: user )} diff --git a/spec/requests/admin/admin_hooks_spec.rb b/spec/requests/admin/admin_hooks_spec.rb index 3f35b2fd37d..bc0586b2712 100644 --- a/spec/requests/admin/admin_hooks_spec.rb +++ b/spec/requests/admin/admin_hooks_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' describe "Admin::Hooks" do before do - @project = create(:project, - name: "LeGiT", - code: "LGT") + @project = create(:project) login_as :admin @system_hook = create(:system_hook) diff --git a/spec/requests/admin/admin_projects_spec.rb b/spec/requests/admin/admin_projects_spec.rb index 43e39d7cbcd..c9ddf1f4534 100644 --- a/spec/requests/admin/admin_projects_spec.rb +++ b/spec/requests/admin/admin_projects_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' describe "Admin::Projects" do before do - @project = create(:project, - name: "LeGiT", - code: "LGT") + @project = create(:project) login_as :admin end @@ -29,7 +27,7 @@ describe "Admin::Projects" do end it "should have project info" do - page.should have_content(@project.code) + page.should have_content(@project.path) page.should have_content(@project.name) end end @@ -41,67 +39,27 @@ describe "Admin::Projects" do end it "should have project edit page" do - page.should have_content("Project name") - page.should have_content("URL") + page.should have_content("Edit project") + page.should have_button("Save Project") end describe "Update project" do before do fill_in "project_name", with: "Big Bang" - fill_in "project_code", with: "BB1" click_button "Save Project" @project.reload end it "should show page with new data" do - page.should have_content("BB1") page.should have_content("Big Bang") end it "should change project entry" do @project.name.should == "Big Bang" - @project.code.should == "BB1" end end end - describe "GET /admin/projects/new" do - before do - visit admin_projects_path - click_link "New Project" - end - - it "should be correct path" do - current_path.should == new_admin_project_path - end - - it "should have labels for new project" do - page.should have_content("Project name is") - page.should have_content("Git Clone") - page.should have_content("URL") - end - end - - describe "POST /admin/projects" do - before do - visit new_admin_project_path - fill_in 'project_name', with: 'NewProject' - fill_in 'project_code', with: 'NPR' - fill_in 'project_path', with: 'gitlabhq_1' - expect { click_button "Create project" }.to change { Project.count }.by(1) - @project = Project.last - end - - it "should be correct path" do - current_path.should == admin_project_path(@project) - end - - it "should show project" do - page.should have_content(@project.name) - page.should have_content(@project.path) - end - end - describe "Add new team member" do before do @new_user = create(:user) diff --git a/spec/requests/admin/admin_users_spec.rb b/spec/requests/admin/admin_users_spec.rb index 9f43f07a476..ca134c2d4f7 100644 --- a/spec/requests/admin/admin_users_spec.rb +++ b/spec/requests/admin/admin_users_spec.rb @@ -23,6 +23,7 @@ describe "Admin::Users" do @password = "123ABC" visit new_admin_user_path fill_in "user_name", with: "Big Bang" + fill_in "user_username", with: "bang" fill_in "user_email", with: "bigbang@mail.com" fill_in "user_password", with: @password fill_in "user_password_confirmation", with: @password diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 6ea7e9b5579..1850ecb95cc 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::API do describe "GET /projects/:id/issues" do it "should return project issues" do - get api("/projects/#{project.code}/issues", user) + get api("/projects/#{project.path}/issues", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == issue.title @@ -37,7 +37,7 @@ describe Gitlab::API do describe "GET /projects/:id/issues/:issue_id" do it "should return a project issue by id" do - get api("/projects/#{project.code}/issues/#{issue.id}", user) + get api("/projects/#{project.path}/issues/#{issue.id}", user) response.status.should == 200 json_response['title'].should == issue.title end @@ -45,7 +45,7 @@ describe Gitlab::API do describe "POST /projects/:id/issues" do it "should create a new project issue" do - post api("/projects/#{project.code}/issues", user), + post api("/projects/#{project.path}/issues", user), title: 'new issue', labels: 'label, label2' response.status.should == 201 json_response['title'].should == 'new issue' @@ -56,7 +56,7 @@ describe Gitlab::API do describe "PUT /projects/:id/issues/:issue_id" do it "should update a project issue" do - put api("/projects/#{project.code}/issues/#{issue.id}", user), + put api("/projects/#{project.path}/issues/#{issue.id}", user), title: 'updated title', labels: 'label2', closed: 1 response.status.should == 200 json_response['title'].should == 'updated title' @@ -67,7 +67,7 @@ describe Gitlab::API do describe "DELETE /projects/:id/issues/:issue_id" do it "should delete a project issue" do - delete api("/projects/#{project.code}/issues/#{issue.id}", user) + delete api("/projects/#{project.path}/issues/#{issue.id}", user) response.status.should == 405 end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index e83f24671ed..43931aedcda 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -11,14 +11,14 @@ describe Gitlab::API do describe "GET /projects/:id/merge_requests" do context "when unauthenticated" do it "should return authentication error" do - get api("/projects/#{project.code}/merge_requests") + get api("/projects/#{project.path}/merge_requests") response.status.should == 401 end end context "when authenticated" do it "should return an array of merge_requests" do - get api("/projects/#{project.code}/merge_requests", user) + get api("/projects/#{project.path}/merge_requests", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == merge_request.title @@ -28,7 +28,7 @@ describe Gitlab::API do describe "GET /projects/:id/merge_request/:merge_request_id" do it "should return merge_request" do - get api("/projects/#{project.code}/merge_request/#{merge_request.id}", user) + get api("/projects/#{project.path}/merge_request/#{merge_request.id}", user) response.status.should == 200 json_response['title'].should == merge_request.title end @@ -36,7 +36,7 @@ describe Gitlab::API do describe "POST /projects/:id/merge_requests" do it "should return merge_request" do - post api("/projects/#{project.code}/merge_requests", user), + post api("/projects/#{project.path}/merge_requests", user), title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user response.status.should == 201 json_response['title'].should == 'Test merge_request' @@ -45,7 +45,7 @@ describe Gitlab::API do describe "PUT /projects/:id/merge_request/:merge_request_id" do it "should return merge_request" do - put api("/projects/#{project.code}/merge_request/#{merge_request.id}", user), title: "New title" + put api("/projects/#{project.path}/merge_request/#{merge_request.id}", user), title: "New title" response.status.should == 200 json_response['title'].should == 'New title' end @@ -53,7 +53,7 @@ describe Gitlab::API do describe "POST /projects/:id/merge_request/:merge_request_id/comments" do it "should return comment" do - post api("/projects/#{project.code}/merge_request/#{merge_request.id}/comments", user), note: "My comment" + post api("/projects/#{project.path}/merge_request/#{merge_request.id}/comments", user), note: "My comment" response.status.should == 201 json_response['note'].should == 'My comment' end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 860825ab2db..dc96d46d894 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::API do describe "GET /projects/:id/milestones" do it "should return project milestones" do - get api("/projects/#{project.code}/milestones", user) + get api("/projects/#{project.path}/milestones", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == milestone.title @@ -20,7 +20,7 @@ describe Gitlab::API do describe "GET /projects/:id/milestones/:milestone_id" do it "should return a project milestone by id" do - get api("/projects/#{project.code}/milestones/#{milestone.id}", user) + get api("/projects/#{project.path}/milestones/#{milestone.id}", user) response.status.should == 200 json_response['title'].should == milestone.title end @@ -28,7 +28,7 @@ describe Gitlab::API do describe "POST /projects/:id/milestones" do it "should create a new project milestone" do - post api("/projects/#{project.code}/milestones", user), + post api("/projects/#{project.path}/milestones", user), title: 'new milestone' response.status.should == 201 json_response['title'].should == 'new milestone' @@ -38,7 +38,7 @@ describe Gitlab::API do describe "PUT /projects/:id/milestones/:milestone_id" do it "should update a project milestone" do - put api("/projects/#{project.code}/milestones/#{milestone.id}", user), + put api("/projects/#{project.path}/milestones/#{milestone.id}", user), title: 'updated title' response.status.should == 200 json_response['title'].should == 'updated title' diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb new file mode 100644 index 00000000000..dc02e7a3efa --- /dev/null +++ b/spec/requests/api/notes_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe Gitlab::API do + include ApiHelpers + + let(:user) { create(:user) } + let!(:project) { create(:project, owner: user) } + let!(:issue) { create(:issue, project: project, author: user) } + let!(:snippet) { create(:snippet, project: project, author: user) } + let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } + let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) } + let!(:wall_note) { create(:note, project: project, author: user) } + before { project.add_access(user, :read) } + + describe "GET /projects/:id/notes" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/projects/#{project.id}/notes") + response.status.should == 401 + end + end + + context "when authenticated" do + it "should return project wall notes" do + get api("/projects/#{project.id}/notes", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['body'].should == wall_note.note + end + end + end + + describe "POST /projects/:id/notes" do + it "should create a new wall note" do + post api("/projects/#{project.id}/notes", user), body: 'hi!' + response.status.should == 201 + json_response['body'].should == 'hi!' + end + end + + describe "GET /projects/:id/noteable/:noteable_id/notes" do + context "when noteable is an Issue" do + it "should return an array of issue notes" do + get api("/projects/#{project.id}/issues/#{issue.id}/notes", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['body'].should == issue_note.note + end + end + + context "when noteable is a Snippet" do + it "should return an array of snippet notes" do + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['body'].should == snippet_note.note + end + end + end + + describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do + context "when noteable is an Issue" do + it "should return an issue note by id" do + get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user) + response.status.should == 200 + json_response['body'].should == issue_note.note + end + end + + context "when noteable is a Snippet" do + it "should return a snippet note by id" do + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) + response.status.should == 200 + json_response['body'].should == snippet_note.note + end + end + end + + describe "POST /projects/:id/noteable/:noteable_id/notes" do + context "when noteable is an Issue" do + it "should create a new issue note" do + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' + response.status.should == 201 + json_response['body'].should == 'hi!' + json_response['author']['email'].should == user.email + end + end + + context "when noteable is a Snippet" do + it "should create a new snippet note" do + post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' + response.status.should == 201 + json_response['body'].should == 'hi!' + json_response['author']['email'].should == user.email + end + end + end +end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index d24ce43d3f2..b4e2fbbdab8 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::API do end describe "POST /projects" do - it "should create new project without code and path" do + it "should create new project without path" do expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1) end @@ -53,8 +53,6 @@ describe Gitlab::API do it "should assign attributes to project" do project = attributes_for(:project, { - path: 'path', - code: 'code', description: Faker::Lorem.sentence, default_branch: 'stable', issues_enabled: false, @@ -79,8 +77,8 @@ describe Gitlab::API do json_response['owner']['email'].should == user.email end - it "should return a project by code name" do - get api("/projects/#{project.code}", user) + it "should return a project by path name" do + get api("/projects/#{project.path}", user) response.status.should == 200 json_response['name'].should == project.name end @@ -94,7 +92,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/branches" do it "should return an array of project branches" do - get api("/projects/#{project.code}/repository/branches", user) + get api("/projects/#{project.path}/repository/branches", user) response.status.should == 200 json_response.should be_an Array json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name @@ -103,7 +101,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/branches/:branch" do it "should return the branch information for a single branch" do - get api("/projects/#{project.code}/repository/branches/new_design", user) + get api("/projects/#{project.path}/repository/branches/new_design", user) response.status.should == 200 json_response['name'].should == 'new_design' @@ -113,7 +111,7 @@ describe Gitlab::API do describe "GET /projects/:id/members" do it "should return project team members" do - get api("/projects/#{project.code}/members", user) + get api("/projects/#{project.path}/members", user) response.status.should == 200 json_response.should be_an Array json_response.count.should == 2 @@ -123,7 +121,7 @@ describe Gitlab::API do describe "GET /projects/:id/members/:user_id" do it "should return project team member" do - get api("/projects/#{project.code}/members/#{user.id}", user) + get api("/projects/#{project.path}/members/#{user.id}", user) response.status.should == 200 json_response['email'].should == user.email json_response['access_level'].should == UsersProject::MASTER @@ -133,7 +131,7 @@ describe Gitlab::API do describe "POST /projects/:id/members" do it "should add user to project team" do expect { - post api("/projects/#{project.code}/members", user), user_id: user2.id, + post api("/projects/#{project.path}/members", user), user_id: user2.id, access_level: UsersProject::DEVELOPER }.to change { UsersProject.count }.by(1) @@ -145,7 +143,7 @@ describe Gitlab::API do describe "PUT /projects/:id/members/:user_id" do it "should update project team member" do - put api("/projects/#{project.code}/members/#{user3.id}", user), access_level: UsersProject::MASTER + put api("/projects/#{project.path}/members/#{user3.id}", user), access_level: UsersProject::MASTER response.status.should == 200 json_response['email'].should == user3.email json_response['access_level'].should == UsersProject::MASTER @@ -155,14 +153,14 @@ describe Gitlab::API do describe "DELETE /projects/:id/members/:user_id" do it "should remove user from project team" do expect { - delete api("/projects/#{project.code}/members/#{user3.id}", user) + delete api("/projects/#{project.path}/members/#{user3.id}", user) }.to change { UsersProject.count }.by(-1) end end describe "GET /projects/:id/hooks" do it "should return project hooks" do - get api("/projects/#{project.code}/hooks", user) + get api("/projects/#{project.path}/hooks", user) response.status.should == 200 @@ -174,7 +172,7 @@ describe Gitlab::API do describe "GET /projects/:id/hooks/:hook_id" do it "should return a project hook" do - get api("/projects/#{project.code}/hooks/#{hook.id}", user) + get api("/projects/#{project.path}/hooks/#{hook.id}", user) response.status.should == 200 json_response['url'].should == hook.url end @@ -183,7 +181,7 @@ describe Gitlab::API do describe "POST /projects/:id/hooks" do it "should add hook to project" do expect { - post api("/projects/#{project.code}/hooks", user), + post api("/projects/#{project.path}/hooks", user), "url" => "http://example.com" }.to change {project.hooks.count}.by(1) end @@ -191,7 +189,7 @@ describe Gitlab::API do describe "PUT /projects/:id/hooks/:hook_id" do it "should update an existing project hook" do - put api("/projects/#{project.code}/hooks/#{hook.id}", user), + put api("/projects/#{project.path}/hooks/#{hook.id}", user), url: 'http://example.org' response.status.should == 200 json_response['url'].should == 'http://example.org' @@ -202,7 +200,7 @@ describe Gitlab::API do describe "DELETE /projects/:id/hooks" do it "should delete hook from project" do expect { - delete api("/projects/#{project.code}/hooks", user), + delete api("/projects/#{project.path}/hooks", user), hook_id: hook.id }.to change {project.hooks.count}.by(-1) end @@ -210,7 +208,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/tags" do it "should return an array of project tags" do - get api("/projects/#{project.code}/repository/tags", user) + get api("/projects/#{project.path}/repository/tags", user) response.status.should == 200 json_response.should be_an Array json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name @@ -222,7 +220,7 @@ describe Gitlab::API do before { project.add_access(user2, :read) } it "should return project commits" do - get api("/projects/#{project.code}/repository/commits", user) + get api("/projects/#{project.path}/repository/commits", user) response.status.should == 200 json_response.should be_an Array @@ -232,7 +230,7 @@ describe Gitlab::API do context "unauthorized user" do it "should not return project commits" do - get api("/projects/#{project.code}/repository/commits") + get api("/projects/#{project.path}/repository/commits") response.status.should == 401 end end @@ -240,7 +238,7 @@ describe Gitlab::API do describe "GET /projects/:id/snippets" do it "should return an array of project snippets" do - get api("/projects/#{project.code}/snippets", user) + get api("/projects/#{project.path}/snippets", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == snippet.title @@ -249,7 +247,7 @@ describe Gitlab::API do describe "GET /projects/:id/snippets/:snippet_id" do it "should return a project snippet" do - get api("/projects/#{project.code}/snippets/#{snippet.id}", user) + get api("/projects/#{project.path}/snippets/#{snippet.id}", user) response.status.should == 200 json_response['title'].should == snippet.title end @@ -257,7 +255,7 @@ describe Gitlab::API do describe "POST /projects/:id/snippets" do it "should create a new project snippet" do - post api("/projects/#{project.code}/snippets", user), + post api("/projects/#{project.path}/snippets", user), title: 'api test', file_name: 'sample.rb', code: 'test' response.status.should == 201 json_response['title'].should == 'api test' @@ -266,7 +264,7 @@ describe Gitlab::API do describe "PUT /projects/:id/snippets/:shippet_id" do it "should update an existing project snippet" do - put api("/projects/#{project.code}/snippets/#{snippet.id}", user), + put api("/projects/#{project.path}/snippets/#{snippet.id}", user), code: 'updated code' response.status.should == 200 json_response['title'].should == 'example' @@ -277,31 +275,31 @@ describe Gitlab::API do describe "DELETE /projects/:id/snippets/:snippet_id" do it "should delete existing project snippet" do expect { - delete api("/projects/#{project.code}/snippets/#{snippet.id}", user) + delete api("/projects/#{project.path}/snippets/#{snippet.id}", user) }.to change { Snippet.count }.by(-1) end end describe "GET /projects/:id/snippets/:snippet_id/raw" do it "should get a raw project snippet" do - get api("/projects/#{project.code}/snippets/#{snippet.id}/raw", user) + get api("/projects/#{project.path}/snippets/#{snippet.id}/raw", user) response.status.should == 200 end end describe "GET /projects/:id/:sha/blob" do it "should get the raw file contents" do - get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.md", user) + get api("/projects/#{project.path}/repository/commits/master/blob?filepath=README.md", user) response.status.should == 200 end it "should return 404 for invalid branch_name" do - get api("/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md", user) + get api("/projects/#{project.path}/repository/commits/invalid_branch_name/blob?filepath=README.md", user) response.status.should == 404 end it "should return 404 for invalid file" do - get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid", user) + get api("/projects/#{project.path}/repository/commits/master/blob?filepath=README.invalid", user) response.status.should == 404 end end diff --git a/spec/requests/gitlab_flavored_markdown_spec.rb b/spec/requests/gitlab_flavored_markdown_spec.rb index aedd435e617..ad5d7cd7607 100644 --- a/spec/requests/gitlab_flavored_markdown_spec.rb +++ b/spec/requests/gitlab_flavored_markdown_spec.rb @@ -197,18 +197,6 @@ describe "Gitlab Flavored Markdown" do page.should have_link("##{issue.id}") end - - it "should render in wikis#index", js: true do - visit project_wiki_path(project, :index) - fill_in "Title", with: 'Test title' - fill_in "Content", with: '[link test](test)' - click_on "Save" - - fill_in "note_note", with: "see ##{issue.id}" - click_button "Add Comment" - - page.should have_link("##{issue.id}") - end end diff --git a/spec/requests/projects_spec.rb b/spec/requests/projects_spec.rb index c44bea89f96..8c0f8e5fbc1 100644 --- a/spec/requests/projects_spec.rb +++ b/spec/requests/projects_spec.rb @@ -3,16 +3,6 @@ require 'spec_helper' describe "Projects" do before { login_as :user } - describe 'GET /project/new' do - it "should work autocomplete", :js => true do - visit new_project_path - - fill_in 'project_name', with: 'Awesome' - find("#project_path").value.should == 'awesome' - find("#project_code").value.should == 'awesome' - end - end - describe "GET /projects/show" do before do @project = create(:project, owner: @user) @@ -53,7 +43,6 @@ describe "Projects" do visit edit_project_path(@project) fill_in 'project_name', with: 'Awesome' - fill_in 'project_code', with: 'gitlabhq' click_button "Save" @project = @project.reload end diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index 60261c7ae71..fb26bf98d0f 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -78,14 +78,6 @@ describe Admin::ProjectsController, "routing" do get("/admin/projects").should route_to('admin/projects#index') end - it "to #create" do - post("/admin/projects").should route_to('admin/projects#create') - end - - it "to #new" do - get("/admin/projects/new").should route_to('admin/projects#new') - end - it "to #edit" do get("/admin/projects/gitlab/edit").should route_to('admin/projects#edit', id: 'gitlab') end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index dc687d2a7ac..25db2f91d4d 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -208,7 +208,6 @@ end # diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) merge_requests#diffs # automerge_project_merge_request GET /:project_id/merge_requests/:id/automerge(.:format) merge_requests#automerge # automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) merge_requests#automerge_check -# raw_project_merge_request GET /:project_id/merge_requests/:id/raw(.:format) merge_requests#raw # branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) merge_requests#branch_from # branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) merge_requests#branch_to # project_merge_requests GET /:project_id/merge_requests(.:format) merge_requests#index @@ -231,10 +230,6 @@ describe MergeRequestsController, "routing" do get("/gitlabhq/merge_requests/1/automerge_check").should route_to('merge_requests#automerge_check', project_id: 'gitlabhq', id: '1') end - it "to #raw" do - get("/gitlabhq/merge_requests/1/raw").should route_to('merge_requests#raw', project_id: 'gitlabhq', id: '1') - end - it "to #branch_from" do get("/gitlabhq/merge_requests/branch_from").should route_to('merge_requests#branch_from', project_id: 'gitlabhq') end @@ -243,6 +238,11 @@ describe MergeRequestsController, "routing" do get("/gitlabhq/merge_requests/branch_to").should route_to('merge_requests#branch_to', project_id: 'gitlabhq') end + it "to #show" do + get("/gitlabhq/merge_requests/1.diff").should route_to('merge_requests#show', project_id: 'gitlabhq', id: '1', format: 'diff') + get("/gitlabhq/merge_requests/1.patch").should route_to('merge_requests#show', project_id: 'gitlabhq', id: '1', format: 'patch') + end + it_behaves_like "RESTful project resources" do let(:controller) { 'merge_requests' } end @@ -285,6 +285,7 @@ end describe CommitController, "routing" do it "to #show" do get("/gitlabhq/commit/4246fb").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb') + get("/gitlabhq/commit/4246fb.diff").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb', format: 'diff') get("/gitlabhq/commit/4246fb.patch").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb', format: 'patch') get("/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ace5ca00cc1..7728b1e9d84 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -40,5 +40,10 @@ RSpec.configure do |config| # !!! Observers disabled by default in tests ActiveRecord::Base.observers.disable(:all) # ActiveRecord::Base.observers.enable(:all) + + # Use tmp dir for FS manipulations + Gitlab.config.stub(git_base_path: Rails.root.join('tmp', 'test-git-base-path')) + FileUtils.rm_rf Gitlab.config.git_base_path + FileUtils.mkdir_p Gitlab.config.git_base_path end end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb new file mode 100644 index 00000000000..d9aa0543969 --- /dev/null +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:app namespace rake task' do + before :all do + Rake.application.rake_require "tasks/gitlab/backup" + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + describe 'backup_restore' do + before do + # avoid writing task output to spec progress + $stdout.stub :write + end + + let :run_rake_task do + Rake::Task["gitlab:app:backup_restore"].reenable + Rake.application.invoke_task "gitlab:app:backup_restore" + end + + context 'gitlab version' do + before do + Dir.stub :glob => [] + Dir.stub :chdir + File.stub :exists? => true + Kernel.stub :system => true + end + + let(:gitlab_version) { %x{git rev-parse HEAD}.gsub(/\n/,"") } + + it 'should fail on mismach' do + YAML.stub :load_file => {:gitlab_version => gitlab_version.reverse} + expect { run_rake_task }.to raise_error SystemExit + end + + it 'should invoke restoration on mach' do + YAML.stub :load_file => {:gitlab_version => gitlab_version} + Rake::Task["gitlab:app:db_restore"].should_receive :invoke + Rake::Task["gitlab:app:repo_restore"].should_receive :invoke + expect { run_rake_task }.to_not raise_error SystemExit + end + end + + end # backup_restore task +end # gitlab:app namespace |
