diff --git a/CHANGELOG b/CHANGELOG index 478e3333bc..cec94f5054 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,25 @@ v 7.9.0 (unreleased) - Improve error messages for file edit failures - Improve UI for commits, issues and merge request lists - Fix commit comments on first line of diff not rendering in Merge Request Discussion view. + - Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev) + - Save web edit in new branch + - Fix ordering of imported but unchanged projects (Marco Wessel) + - Mobile UI improvements: make aside content expandable + - Expose avatar_url in projects API + - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) + - Fix mass-unassignment of issues (Robert Speicher) + - Allow user confirmation to be skipped for new users via API + - Add a service to send updates to an Irker gateway (Romain Coltel) + +v 7.8.1 + - Fix run of custom post receive hooks + - Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3 + - Fix the warning for LDAP users about need to set password + - Fix avatars which were not shown for non logged in users + - Fix urls for the issues when relative url was enabled + - Add Bitbucket omniauth provider. + - Add Bitbucket importer. + - Support referencing issues to a project whose name starts with a digit v 7.8.0 - Fix access control and protection against XSS for note attachments and other uploads. @@ -37,7 +56,7 @@ v 7.8.0 - Allow configuring protection of the default branch upon first push (Marco Wessel) - Add gitlab.com importer - Add an ability to login with gitlab.com - - Add a commit calendar to the user profile (Hannes Rosenögger) + - Add a commit calendar to the user profile (Hannes Rosenögger) - Submit comment on command-enter - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`. - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" @@ -52,7 +71,7 @@ v 7.8.0 - API: Access groups with their path (Julien Bianchi) - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard) - Allow notification email to be set separately from primary email. - - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) + - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) - Don't have Markdown preview fail for long comments/wiki pages. - When test web hook - show error message instead of 500 error page if connection to hook url was reset - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) @@ -72,6 +91,7 @@ v 7.8.0 - Improve database performance for GitLab - Add Asana service (Jeremy Benoist) - Improve project web hooks with extra data + - Slack username and channel options v 7.7.2 - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch diff --git a/Gemfile b/Gemfile index beddc4fb40..b02b296bfa 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem 'omniauth-github' gem 'omniauth-shibboleth' gem 'omniauth-kerberos' gem 'omniauth-gitlab' +gem 'omniauth-bitbucket' gem 'doorkeeper', '2.1.0' gem "rack-oauth2", "~> 1.0.5" @@ -41,7 +42,7 @@ gem "browser" gem "gitlab_git", '7.0.0.rc14' # Ruby/Rack Git Smart-HTTP Server Handler -gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' +gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack' # LDAP Auth gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap" @@ -51,7 +52,7 @@ gem 'net-ldap' gem 'gollum-lib', '~> 4.0.0' # Language detection -gem "gitlab-linguist", "~> 3.0.0", require: "linguist" +gem "gitlab-linguist", "~> 3.0.1", require: "linguist" # API gem "grape", "~> 0.6.1" @@ -204,6 +205,7 @@ group :development do gem "letter_opener" gem 'quiet_assets', '~> 1.0.1' gem 'rack-mini-profiler', require: false + gem "byebug" # Better errors handler gem 'better_errors' diff --git a/Gemfile.lock b/Gemfile.lock index b342c85cb6..8806ec7c6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -65,6 +65,9 @@ GEM sass (>= 3.2.19) browser (0.7.2) builder (3.2.2) + byebug (3.2.0) + columnize (~> 0.8) + debugger-linecache (~> 1.2) cal-heatmap-rails (0.0.1) capybara (2.2.1) mime-types (>= 1.16) @@ -78,7 +81,7 @@ GEM json (>= 1.7) celluloid (0.16.0) timers (~> 4.0.0) - charlock_holmes (0.7.3) + charlock_holmes (0.6.9.4) cliver (0.3.2) coderay (1.1.0) coercible (1.0.0) @@ -92,6 +95,7 @@ GEM coffee-script-source (1.6.3) colored (1.2) colorize (0.5.8) + columnize (0.9.0) connection_pool (2.1.0) coveralls (0.7.0) multi_json (~> 1.3) @@ -107,6 +111,7 @@ GEM daemons (1.1.9) database_cleaner (1.3.0) debug_inspector (0.0.2) + debugger-linecache (1.2.0) default_value_for (3.0.0) activerecord (>= 3.2.0, < 5.0) descendants_tracker (0.0.3) @@ -174,8 +179,8 @@ GEM dotenv (>= 0.7) thor (>= 0.13.6) formatador (0.2.4) - gemnasium-gitlab-service (0.2.3) - rugged (~> 0.19) + gemnasium-gitlab-service (0.2.4) + rugged (~> 0.21) gherkin-ruby (0.3.1) racc github-markup (1.3.1) @@ -183,14 +188,14 @@ GEM gitlab-flowdock-git-hook (0.4.2.2) gitlab-grit (>= 2.4.1) multi_json - gitlab-grack (2.0.0.pre) + gitlab-grack (2.0.0.rc2) rack (~> 1.5.1) gitlab-grit (2.7.2) charlock_holmes (~> 0.6) diff-lcs (~> 1.1) mime-types (~> 1.15) posix-spawn (~> 0.3) - gitlab-linguist (3.0.0) + gitlab-linguist (3.0.1) charlock_holmes (~> 0.6.6) escape_utils (~> 0.2.4) mime-types (~> 1.19) @@ -339,6 +344,10 @@ GEM omniauth (1.1.4) hashie (>= 1.2, < 3) rack + omniauth-bitbucket (0.0.2) + multi_json (~> 1.7) + omniauth (~> 1.1) + omniauth-oauth (~> 1.0) omniauth-github (1.1.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) @@ -490,7 +499,7 @@ GEM ruby-progressbar (1.7.1) rubyntlm (0.4.0) rubypants (0.2.0) - rugged (0.21.2) + rugged (0.21.4) rugments (1.0.0.beta3) safe_yaml (0.9.7) sanitize (2.1.0) @@ -613,7 +622,7 @@ GEM raindrops (~> 0.7) unicorn-worker-killer (0.4.2) unicorn (~> 4) - version_sorter (1.1.0) + version_sorter (2.0.0) virtus (1.0.1) axiom-types (~> 0.0.5) coercible (~> 1.0) @@ -648,6 +657,7 @@ DEPENDENCIES binding_of_caller bootstrap-sass (~> 3.0) browser + byebug cal-heatmap-rails (~> 0.0.1) capybara (~> 2.2.1) carrierwave @@ -673,8 +683,8 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) github-markup gitlab-flowdock-git-hook (~> 0.4.2) - gitlab-grack (~> 2.0.0.pre) - gitlab-linguist (~> 3.0.0) + gitlab-grack (~> 2.0.0.rc2) + gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.0.1.1) gitlab_git (= 7.0.0.rc14) gitlab_meta (= 7.0) @@ -707,6 +717,7 @@ DEPENDENCIES nprogress-rails octokit (= 3.7.0) omniauth (~> 1.1.3) + omniauth-bitbucket omniauth-github omniauth-gitlab omniauth-google-oauth2 diff --git a/app/assets/images/authbuttons/bitbucket_32.png b/app/assets/images/authbuttons/bitbucket_32.png new file mode 100644 index 0000000000..27702eb973 Binary files /dev/null and b/app/assets/images/authbuttons/bitbucket_32.png differ diff --git a/app/assets/images/authbuttons/bitbucket_64.png b/app/assets/images/authbuttons/bitbucket_64.png new file mode 100644 index 0000000000..4b90a57bc7 Binary files /dev/null and b/app/assets/images/authbuttons/bitbucket_64.png differ diff --git a/app/assets/images/gitorious-logo-black.png b/app/assets/images/gitorious-logo-black.png new file mode 100644 index 0000000000..78f17a9af7 Binary files /dev/null and b/app/assets/images/gitorious-logo-black.png differ diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png new file mode 100644 index 0000000000..4962cffba3 Binary files /dev/null and b/app/assets/images/gitorious-logo-blue.png differ diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png deleted file mode 100644 index 49cdc16cac..0000000000 Binary files a/app/assets/images/logo-black.png and /dev/null differ diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png index 2299153cab..917bcfcb7e 100644 Binary files a/app/assets/images/logo-white.png and b/app/assets/images/logo-white.png differ diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee index b0b312e774..05f5af4257 100644 --- a/app/assets/javascripts/diff.js.coffee +++ b/app/assets/javascripts/diff.js.coffee @@ -1,6 +1,7 @@ class @Diff UNFOLD_COUNT = 20 constructor: -> + $(document).off('click', '.js-unfold') $(document).on('click', '.js-unfold', (event) => target = $(event.target) unfoldBottom = target.hasClass('js-unfold-bottom') @@ -36,7 +37,7 @@ class @Diff ) ) - $('.diff-header').stick_in_parent(offset_top: $('.navbar').height()) + $('.diff-header').stick_in_parent(recalc_every: 1, offset_top: $('.navbar').height()) lineNumbers: (line) -> return ([0, 0]) unless line.children().length diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index d98d548293..06e9f0001a 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -6,10 +6,10 @@ class @DropzoneInput divHover = "
" divSpinner = "" divAlert = "" - iconPicture = "" + iconPaperclip = "" iconSpinner = "" btnAlert = "" - project_image_path_upload = window.project_image_path_upload or null + project_uploads_path = window.project_uploads_path or null form_textarea = $(form).find("textarea.markdown-area") form_textarea.wrap "" @@ -19,7 +19,7 @@ class @DropzoneInput form_dropzone = $(form).find('.div-dropzone') form_dropzone.parent().addClass "div-dropzone-wrapper" form_dropzone.append divHover - $(".div-dropzone-hover").append iconPicture + $(".div-dropzone-hover").append iconPaperclip form_dropzone.append divSpinner $(".div-dropzone-spinner").append iconSpinner $(".div-dropzone-spinner").css @@ -72,13 +72,12 @@ class @DropzoneInput form.find(".md-preview-holder").hide() dropzone = form_dropzone.dropzone( - url: project_image_path_upload + url: project_uploads_path dictDefaultMessage: "" clickable: true - paramName: "markdown_img" + paramName: "file" maxFilesize: 10 uploadMultiple: false - acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png" headers: "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") @@ -132,8 +131,10 @@ class @DropzoneInput child = $(dropzone[0]).children("textarea") - formatLink = (str) -> - "" + formatLink = (link) -> + text = "[#{link.alt}](#{link.url})" + text = "!#{text}" if link.is_image + text handlePaste = (event) -> pasteEvent = event.originalEvent @@ -177,9 +178,9 @@ class @DropzoneInput uploadFile = (item, filename) -> formData = new FormData() - formData.append "markdown_img", item, filename + formData.append "file", item, filename $.ajax - url: project_image_path_upload + url: project_uploads_path type: "POST" data: formData dataType: "json" @@ -233,5 +234,7 @@ class @DropzoneInput $(@).closest('.gfm-form').find('.div-dropzone').click() return - formatLink: (str) -> - "" + formatLink: (link) -> + text = "[#{link.alt}](#{link.url})" + text = "!#{text}" if link.is_image + text \ No newline at end of file diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 9b7c1be835..f275317047 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -16,8 +16,9 @@ class @Issue updateTaskState ) - $('.issuable-affix').affix offset: - top: -> - @top = $('.issue-details').outerHeight(true) + 25 - bottom: -> - @bottom = $('.footer').outerHeight(true) + $('.issue-details').waitForImages -> + $('.issuable-affix').affix offset: + top: -> + @top = $('.issue-details').outerHeight(true) + 25 + bottom: -> + @bottom = $('.footer').outerHeight(true) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 757592842e..1fee9dc189 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -20,11 +20,12 @@ class @MergeRequest if $("a.btn-close").length $("li.task-list-item input:checkbox").prop("disabled", false) - $('.issuable-affix').affix offset: - top: -> - @top = $('.merge-request-details').outerHeight(true) + 70 - bottom: -> - @bottom = $('.footer').outerHeight(true) + $('.merge-request-details').waitForImages -> + $('.issuable-affix').affix offset: + top: -> + @top = $('.merge-request-details').outerHeight(true) + 91 + bottom: -> + @bottom = $('.footer').outerHeight(true) # Local jQuery finder $: (selector) -> @@ -95,6 +96,7 @@ class @MergeRequest this.$('.merge-request-tabs .diffs-tab').addClass 'active' this.loadDiff() unless @diffs_loaded this.$('.diffs').show() + $(".diff-header").trigger("sticky_kit:recalc") when 'commits' this.$('.merge-request-tabs .commits-tab').addClass 'active' this.$('.commits').show() @@ -123,7 +125,7 @@ class @MergeRequest loadDiff: (event) -> $.ajax type: 'GET' - url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json" beforeSend: => this.$('.mr-loading-status .loading').show() complete: => diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 1c090bd06d..90e6fd6d15 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -39,9 +39,6 @@ class @Notes # reset main target form after submit $(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm - # attachment button - $(document).on "click", ".js-choose-note-attachment-button", @chooseNoteAttachment - # update the file name when an attachment is selected $(document).on "change", ".js-note-attachment-input", @updateFormAttachment @@ -73,7 +70,6 @@ class @Notes $(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-attachment-delete" $(document).off "ajax:complete", ".js-main-target-form" - $(document).off "click", ".js-choose-note-attachment-button" $(document).off "click", ".js-discussion-reply-button" $(document).off "click", ".js-add-diff-note-button" $(document).off "visibilitychange" @@ -173,15 +169,6 @@ class @Notes form.find(".js-note-text").data("autosave").reset() - ### - Called when clicking the "Choose File" button. - - Opens the file selection dialog. - ### - chooseNoteAttachment: -> - form = $(this).closest("form") - form.find(".js-note-attachment-input").click() - ### Shows the main form and does some setup on it. diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee index 7fb3392609..885f0d58a6 100644 --- a/app/assets/javascripts/project_users_select.js.coffee +++ b/app/assets/javascripts/project_users_select.js.coffee @@ -15,7 +15,7 @@ class @ProjectUsersSelect name: 'Unassigned', avatar: null, username: 'none', - id: '' + id: -1 } data.results.unshift(nullUser) diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index 2bb69f4aa7..b3727c3367 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -69,5 +69,6 @@ background: #EEE; font-size: 20px; color: #777; + z-index: 100; @include box-shadow(0 1px 2px #DDD); } diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss index f47ea32982..54311a6885 100644 --- a/app/assets/stylesheets/sections/diff.scss +++ b/app/assets/stylesheets/sections/diff.scss @@ -8,6 +8,7 @@ border-bottom: 1px solid #CCC; padding: 5px 5px 5px 10px; color: #555; + z-index: 10; > span { font-family: $monospace_font; diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index b761451321..a477359dc8 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -184,6 +184,12 @@ } } -.event_filter li a { - padding: 5px 10px; +.event_filter { + + li a { + padding: 5px 10px; + background: rgba(0,0,0,0.045); + margin-left: 4px; + } + } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index e255cbcada..26b4d04106 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -86,7 +86,7 @@ header { .container { width: 100% !important; - padding-left: 0px; + padding: 0px; } /** @@ -100,18 +100,14 @@ header { a { float: left; - padding: 0px; - margin: 0 6px; + padding: 5px 0; + height: 46px; + width: 52px; + text-align: center; - h1 { - margin: 0; - background: image-url('logo-black.png') no-repeat center center; - background-size: 32px; - float: left; - height: 46px; - width: 40px; - @include header-font; - text-indent: -9999px; + img { + width: 36px; + height: 36px; } } &:hover { @@ -134,14 +130,13 @@ header { } .profile-pic { - position: relative; - top: -1px; - padding-right: 0px !important; + padding: 0px !important; + width: 46px; + height: 46px; + margin-left: 5px; img { - width: 50px; - height: 50px; - margin: -15px; - margin-left: 5px; + width: 46px; + height: 46px; } } @@ -174,68 +169,6 @@ header { @include transition(all 0.15s ease-in 0s); } } - - - /* - * Dark header - * - */ - &.header-dark { - &.navbar-gitlab { - .navbar-inner { - background: #708090; - border-bottom: 1px solid #AAA; - - .navbar-toggle { color: #fff; } - - .nav > li > a { - color: #AAA; - - &:hover, &:focus, &:active { - background: none; - color: #FFF; - } - } - } - } - - .turbolink-spinner { - color: #FFF; - } - - .search { - .search-input { - background-color: #D2D5DA; - background-color: rgba(255, 255, 255, 0.5); - border: 1px solid #AAA; - - &:focus { - background-color: white; - } - } - } - .search-input::-webkit-input-placeholder { - color: #666; - } - .app_logo { - a { - h1 { - background: image-url('logo-white.png') no-repeat center center; - background-size: 32px; - color: #fff; - } - } - } - .title { - a { - color: #FFF; - &:hover { - text-decoration: underline; - } - } - color: #fff; - } - } } .search .search-input { diff --git a/app/assets/stylesheets/sections/import.scss b/app/assets/stylesheets/sections/import.scss new file mode 100644 index 0000000000..3df4bb84bd --- /dev/null +++ b/app/assets/stylesheets/sections/import.scss @@ -0,0 +1,18 @@ +i.icon-gitorious { + display: inline-block; + background-position: 0px 0px; + background-size: contain; + background-repeat: no-repeat; +} + +i.icon-gitorious-small { + background-image: image-url('gitorious-logo-blue.png'); + width: 13px; + height: 13px; +} + +i.icon-gitorious-big { + background-image: image-url('gitorious-logo-black.png'); + width: 18px; + height: 18px; +} diff --git a/app/assets/stylesheets/sections/nav_sidebar.scss b/app/assets/stylesheets/sections/nav_sidebar.scss index 8e02b37507..335f137966 100644 --- a/app/assets/stylesheets/sections/nav_sidebar.scss +++ b/app/assets/stylesheets/sections/nav_sidebar.scss @@ -108,7 +108,7 @@ width: $sidebar_width; .nav-sidebar { - margin-top: 20px; + margin-top: 29px; position: fixed; top: 45px; width: $sidebar_width; @@ -127,7 +127,7 @@ width: 52px; .nav-sidebar { - margin-top: 20px; + margin-top: 29px; position: fixed; top: 45px; width: 52px; @@ -144,14 +144,22 @@ } } } + + .collapse-nav a { + left: 0px; + padding: 5px 23px 3px 22px; + } } } .collapse-nav a { position: fixed; - bottom: 15px; - padding: 10px; - background: #DDD; + top: 47px; + padding: 5px 13px 3px 13px; + left: 197px; + background: #EEE; + color: black; + border: 1px solid rgba(0,0,0,0.035); } @media (max-width: $screen-md-max) { diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 5494845eb8..40adc8b3ba 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -66,6 +66,22 @@ ul.notes { overflow: auto; word-wrap: break-word; @include md-typography; + + a[href*="/uploads/"] { + &:before { + margin-right: 4px; + + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + content: "\f0c6"; + } + + &:hover:before { + text-decoration: none; + } + } } } .note-header { diff --git a/app/assets/stylesheets/themes/dark-theme.scss b/app/assets/stylesheets/themes/dark-theme.scss new file mode 100644 index 0000000000..b7b22a8724 --- /dev/null +++ b/app/assets/stylesheets/themes/dark-theme.scss @@ -0,0 +1,63 @@ +@mixin dark-theme($color-light, $color, $color-darker, $color-dark) { + header { + &.navbar-gitlab { + .navbar-inner { + background: $color; + + .navbar-toggle { + color: #FFF; + } + + .app_logo, .navbar-toggle { + &:hover { + background-color: $color-darker; + } + } + + .app_logo { + background-color: $color-dark; + } + + .title { + color: #FFF; + + a { + color: #FFF; + &:hover { + text-decoration: underline; + } + } + } + + .search { + .search-input { + background-color: $color-light; + background-color: rgba(255, 255, 255, 0.5); + border: 1px solid $color-light; + + &:focus { + background-color: white; + } + } + } + + .search-input::-webkit-input-placeholder { + color: #666; + } + + .nav > li > a { + color: $color-light; + + &:hover, &:focus, &:active { + background: none; + color: #FFF; + } + } + + .search-input { + border-color: $color-light; + } + } + } + } +} diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss index 0dad9917b5..097d5c5b73 100644 --- a/app/assets/stylesheets/themes/ui_basic.scss +++ b/app/assets/stylesheets/themes/ui_basic.scss @@ -10,8 +10,15 @@ background: #F1F1F1; border-bottom: 1px solid #DDD; - .app_logo { - background-color: #DDD; + .title { + color: #555; + + a { + color: #555; + &:hover { + text-decoration: underline; + } + } } .nav > li > a { diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss index 3c441a8e09..7ac6903b2e 100644 --- a/app/assets/stylesheets/themes/ui_color.scss +++ b/app/assets/stylesheets/themes/ui_color.scss @@ -1,42 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Violet GitLab UI theme */ .ui_color { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #548; - border-bottom: 1px solid #436; - .app_logo, .navbar-toggle { - &:hover { - background-color: #436; - } - } - .app_logo { - background-color: #325; - } - .nav > li > a { - color: #98C; - } - .search-input { - border-color: #98C; - } - } - } - } - - .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { - background: #659; - } + @include dark-theme(#98C, #548, #436, #325); } diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss index 8df08ccaee..9257e5f4d4 100644 --- a/app/assets/stylesheets/themes/ui_gray.scss +++ b/app/assets/stylesheets/themes/ui_gray.scss @@ -1,32 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Gray GitLab UI theme */ .ui_gray { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #373737; - border-bottom: 1px solid #272727; - .app_logo, .navbar-toggle { - &:hover { - background-color: #272727; - } - } - .app_logo { - background-color: #222; - } - } - } - } + @include dark-theme(#979797, #373737, #272727, #222222); } diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index b08cbda6c4..4caf5843d9 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -1,38 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Classic GitLab UI theme */ .ui_mars { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #474D57; - border-bottom: 1px solid #373D47; - .app_logo, .navbar-toggle { - &:hover { - background-color: #373D47; - } - } - .app_logo { - background-color: #24272D; - } - .nav > li > a { - color: #979DA7; - } - .search-input { - border-color: #979DA7; - } - } - } - } + @include dark-theme(#979DA7, #474D57, #373D47, #24272D); } diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss index 34f39614ca..7044988231 100644 --- a/app/assets/stylesheets/themes/ui_modern.scss +++ b/app/assets/stylesheets/themes/ui_modern.scss @@ -1,42 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Modern GitLab UI theme */ .ui_modern { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #019875; - border-bottom: 1px solid #019875; - .app_logo, .navbar-toggle { - &:hover { - background-color: #018865; - } - } - .app_logo { - background-color: #017855; - } - .nav > li > a { - color: #ADC; - } - .search-input { - border-color: #8ba; - } - } - } - } - - .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { - background: #019875; - } + @include dark-theme(#ADC, #019875, #018865, #017855); } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 454899d1a8..82458aa258 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,7 @@ require 'gon' class ApplicationController < ActionController::Base include Gitlab::CurrentSettings + include GitlabRoutingHelper before_filter :authenticate_user_from_token! before_filter :authenticate_user! @@ -16,6 +17,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :abilities, :can?, :current_application_settings + helper_method :github_import_enabled?, :gitlab_import_enabled?, :bitbucket_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -319,4 +321,16 @@ class ApplicationController < ActionController::Base set_filter_values(merge_requests) merge_requests end + + def github_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:github) + end + + def gitlab_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:gitlab) + end + + def bitbucket_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:bitbucket) && Gitlab::BitbucketImport.public_key.present? + end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index eca7b39bcd..4930029e16 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -47,7 +47,7 @@ class DashboardController < ApplicationController @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? - @projects = @projects.includes(:namespace) + @projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]).per(30) diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb deleted file mode 100644 index 9671245d3f..0000000000 --- a/app/controllers/files_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -class FilesController < ApplicationController - def download - note = Note.find(params[:id]) - uploader = note.attachment - - if uploader.file_storage? - if can?(current_user, :read_project, note.project) - disposition = uploader.image? ? 'inline' : 'attachment' - send_file uploader.file.path, disposition: disposition - else - not_found! - end - else - redirect_to uploader.url - end - end -end diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb new file mode 100644 index 0000000000..83ebc5fddc --- /dev/null +++ b/app/controllers/import/bitbucket_controller.rb @@ -0,0 +1,79 @@ +class Import::BitbucketController < Import::BaseController + before_filter :verify_bitbucket_import_enabled + before_filter :bitbucket_auth, except: :callback + + rescue_from OAuth::Error, with: :bitbucket_unauthorized + + def callback + request_token = session.delete(:oauth_request_token) + raise "Session expired!" if request_token.nil? + + request_token.symbolize_keys! + + access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url) + + current_user.bitbucket_access_token = access_token.token + current_user.bitbucket_access_token_secret = access_token.secret + + current_user.save + redirect_to status_import_bitbucket_url + end + + def status + @repos = client.projects + + @already_added_projects = current_user.created_projects.where(import_type: "bitbucket") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.to_a.reject!{ |repo| already_added_projects_names.include? "#{repo["owner"]}/#{repo["slug"]}" } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "bitbucket").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id] || "" + repo = client.project(@repo_id.gsub("___", "/")) + @target_namespace = params[:new_namespace].presence || repo["owner"] + @project_name = repo["slug"] + + namespace = get_or_create_namespace || (render and return) + + unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user).execute + @access_denied = true + render + return + end + + @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user).execute + end + + private + + def client + @client ||= Gitlab::BitbucketImport::Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + end + + def verify_bitbucket_import_enabled + not_found! unless bitbucket_import_enabled? + end + + def bitbucket_auth + if current_user.bitbucket_access_token.blank? + go_to_bitbucket_for_permissions + end + end + + def go_to_bitbucket_for_permissions + request_token = client.request_token(callback_import_bitbucket_url) + session[:oauth_request_token] = request_token + + redirect_to client.authorize_url(request_token, callback_import_bitbucket_url) + end + + def bitbucket_unauthorized + go_to_bitbucket_for_permissions + end +end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index c869c7c86f..dc7668ee6f 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -1,4 +1,5 @@ class Import::GithubController < Import::BaseController + before_filter :verify_github_import_enabled before_filter :github_auth, except: :callback rescue_from Octokit::Unauthorized, with: :github_unauthorized @@ -44,6 +45,10 @@ class Import::GithubController < Import::BaseController @client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token) end + def verify_github_import_enabled + not_found! unless github_import_enabled? + end + def github_auth if current_user.github_access_token.blank? go_to_github_for_permissions diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index a51ea36aff..e979dad4b1 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -1,4 +1,5 @@ class Import::GitlabController < Import::BaseController + before_filter :verify_gitlab_import_enabled before_filter :gitlab_auth, except: :callback rescue_from OAuth2::Error, with: :gitlab_unauthorized @@ -16,7 +17,7 @@ class Import::GitlabController < Import::BaseController @already_added_projects = current_user.created_projects.where(import_type: "gitlab") already_added_projects_names = @already_added_projects.pluck(:import_source) - @repos.to_a.reject!{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } + @repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } end def jobs @@ -41,6 +42,10 @@ class Import::GitlabController < Import::BaseController @client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token) end + def verify_gitlab_import_enabled + not_found! unless gitlab_import_enabled? + end + def gitlab_auth if current_user.gitlab_access_token.blank? go_to_gitlab_for_permissions diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb index 627b4a171b..6067a87ee0 100644 --- a/app/controllers/import/gitorious_controller.rb +++ b/app/controllers/import/gitorious_controller.rb @@ -15,7 +15,7 @@ class Import::GitoriousController < Import::BaseController @already_added_projects = current_user.created_projects.where(import_type: "gitorious") already_added_projects_names = @already_added_projects.pluck(:import_source) - @repos.to_a.reject! { |repo| already_added_projects_names.include? repo.full_name } + @repos.reject! { |repo| already_added_projects_names.include? repo.full_name } end def jobs diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index b90a95c3aa..a482b90880 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -24,6 +24,6 @@ class Projects::AvatarsController < Projects::ApplicationController @project.save @project.reset_events_cache - redirect_to edit_namespace_project_path(@project.namespace, @project) + redirect_to edit_project_path(@project) end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 1207548eae..4b7eb4df29 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -1,6 +1,7 @@ # Controller for viewing a file's blame class Projects::BlobController < Projects::ApplicationController include ExtractsPath + include ActionView::Helpers::SanitizeHelper # Raised when given an invalid file path class InvalidPathError < StandardError; end @@ -21,11 +22,18 @@ class Projects::BlobController < Projects::ApplicationController def create file_path = File.join(@path, File.basename(params[:file_name])) - result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute + result = Files::CreateService.new( + @project, + current_user, + params.merge(new_branch: sanitized_new_branch_name), + @ref, + file_path + ).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@ref, file_path)) + ref = sanitized_new_branch_name.presence || @ref + redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path)) else flash[:alert] = result[:message] render :new @@ -41,7 +49,13 @@ class Projects::BlobController < Projects::ApplicationController def update result = Files::UpdateService. - new(@project, current_user, params, @ref, @path).execute + new( + @project, + current_user, + params.merge(new_branch: sanitized_new_branch_name), + @ref, + @path + ).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" @@ -131,6 +145,8 @@ class Projects::BlobController < Projects::ApplicationController if from_merge_request diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + "#file-path-#{hexdigest(@path)}" + elsif sanitized_new_branch_name.present? + namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path)) else namespace_project_blob_path(@project.namespace, @project, @id) end @@ -140,4 +156,8 @@ class Projects::BlobController < Projects::ApplicationController # If blob edit was initiated from merge request page @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) end + + def sanitized_new_branch_name + @new_branch ||= sanitize(strip_tags(params[:new_branch])) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 73b58285c6..6a2af08a19 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -60,8 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController respond_to do |format| format.html do if @issue.valid? - redirect_to namespace_project_issue_path(@project.namespace, - @project, @issue) + redirect_to issue_path(@issue) else render :new end @@ -79,7 +78,7 @@ class Projects::IssuesController < Projects::ApplicationController format.js format.html do if @issue.valid? - redirect_to [@project.namespace.becomes(Namespace), @project, @issue] + redirect_to issue_path(@issue) else render :edit end @@ -129,8 +128,7 @@ class Projects::IssuesController < Projects::ApplicationController issue = @project.issues.find_by(id: params[:id]) if issue - redirect_to namespace_project_issue_path(@project.namespace, @project, - issue) + redirect_to issue_path(issue) return else raise ActiveRecord::RecordNotFound.new diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 98e4775e40..26d4c51773 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -79,9 +79,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController if @merge_request.valid? redirect_to( - namespace_project_merge_request_path(@merge_request.target_project.namespace, - @merge_request.target_project, - @merge_request), + merge_request_path(@merge_request), notice: 'Merge request was successfully created.' ) else diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 97eaabb15c..afdb560e73 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -54,7 +54,8 @@ class Projects::MilestonesController < Projects::ApplicationController format.js format.html do if @milestone.valid? - redirect_to [@project, @milestone] + redirect_to namespace_project_milestone_path(@project.namespace, + @project, @milestone) else render :edit end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 245dfb7bb9..cbb888b25e 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -7,7 +7,7 @@ class Projects::RepositoriesController < Projects::ApplicationController def create @project.create_repository - redirect_to namespace_project_path(@project.namespace, @project) + redirect_to project_path(@project) end def archive diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 5c29a6550f..82aad329c1 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -50,7 +50,8 @@ class Projects::ServicesController < Projects::ApplicationController :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :build_key, :server, :teamcity_url, :build_type, - :description, :issues_url, :new_issue_url, :restrict_to_branch + :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, + :colorize_messages, :channels ) end end diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index 2b4da35bc7..9020e86c44 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -1,19 +1,35 @@ class Projects::UploadsController < Projects::ApplicationController - layout "project" + layout 'project' before_filter :project + def create + link_to_file = ::Projects::UploadService.new(project, params[:file]). + execute + + respond_to do |format| + if link_to_file + format.json do + render json: { link: link_to_file } + end + else + format.json do + render json: 'Invalid file.', status: :unprocessable_entity + end + end + end + end + def show - path = File.join(project.path_with_namespace, params[:secret]) - uploader = FileUploader.new('uploads', path) + uploader = FileUploader.new(project, params[:secret]) + + return redirect_to uploader.url unless uploader.file_storage? uploader.retrieve_from_store!(params[:filename]) - if uploader.file.exists? - # Right now, these are always images, so we can safely render them inline. - send_file uploader.file.path, disposition: 'inline' - else - not_found! - end + return not_found! unless uploader.file.exists? + + disposition = uploader.image? ? 'inline' : 'attachment' + send_file uploader.file.path, disposition: disposition end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 3ef429b2e9..155c0b2d4e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -23,7 +23,7 @@ class ProjectsController < ApplicationController if @project.saved? redirect_to( - namespace_project_path(@project.namespace, @project), + project_path(@project), notice: 'Project was successfully created.' ) else @@ -39,7 +39,7 @@ class ProjectsController < ApplicationController flash[:notice] = 'Project was successfully updated.' format.html do redirect_to( - edit_namespace_project_path(@project.namespace, @project), + edit_project_path(@project), notice: 'Project was successfully updated.' ) end @@ -102,7 +102,7 @@ class ProjectsController < ApplicationController flash[:alert] = 'Project deleted.' if request.referer.include?('/admin') - redirect_to admin_namespace_projects_path + redirect_to admin_namespaces_projects_path else redirect_to projects_dashboard_path end @@ -133,7 +133,7 @@ class ProjectsController < ApplicationController @project.archive! respond_to do |format| - format.html { redirect_to namespace_project_path(@project.namespace, @project) } + format.html { redirect_to project_path(@project) } end end @@ -142,19 +142,7 @@ class ProjectsController < ApplicationController @project.unarchive! respond_to do |format| - format.html { redirect_to namespace_project_path(@project.namespace, @project) } - end - end - - def upload_image - link_to_image = ::Projects::ImageService.new(repository, params, root_url).execute - - respond_to do |format| - if link_to_image - format.json { render json: { link: link_to_image } } - else - format.json { render json: 'Invalid file.', status: :unprocessable_entity } - end + format.html { redirect_to project_path(@project) } end end @@ -170,15 +158,6 @@ class ProjectsController < ApplicationController private - def upload_path - base_dir = FileUploader.generate_dir - File.join(repository.path_with_namespace, base_dir) - end - - def accepted_images - %w(png jpg jpeg gif) - end - def set_title @title = 'New Project' end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 73b124bb34..b096c3913e 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -1,26 +1,24 @@ class UploadsController < ApplicationController - skip_before_filter :authenticate_user!, :reject_blocked + skip_before_filter :authenticate_user!, :reject_blocked! before_filter :authorize_access def show model = params[:model].camelize.constantize.find(params[:id]) uploader = model.send(params[:mounted_as]) - if uploader.file_storage? - if !model.respond_to?(:project) || can?(current_user, :read_project, model.project) - disposition = uploader.image? ? 'inline' : 'attachment' - send_file uploader.file.path, disposition: disposition - else - not_found! - end - else - redirect_to uploader.url - end + return not_found! if model.respond_to?(:project) && !can?(current_user, :read_project, model.project) + + return redirect_to uploader.url unless uploader.file_storage? + + return not_found! unless uploader.file.exists? + + disposition = uploader.image? ? 'inline' : 'attachment' + send_file uploader.file.path, disposition: disposition end def authorize_access unless params[:mounted_as] == 'avatar' - authenticate_user! && reject_blocked + authenticate_user! && reject_blocked! end end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4c2fe4c3c8..8a13394dba 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -6,7 +6,9 @@ class UsersController < ApplicationController def show @contributed_projects = Project. where(id: authorized_projects_ids & @user.contributed_projects_ids). - in_group_namespace.includes(:namespace) + in_group_namespace. + includes(:namespace). + reject(&:forked?) @projects = @user.personal_projects. where(id: authorized_projects_ids).includes(:namespace) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index e147751006..088a766ed3 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -44,7 +44,7 @@ class IssuableFinder table_name = klass.table_name if project - if project.public? || (current_user && current_user.can?(:read_project, project)) + if Ability.abilities.allowed?(current_user, :read_project, project) project.send(table_name) else [] diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index 502c9c7cd0..aa82b7ae65 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -37,4 +37,8 @@ module AppearancesHelper def brand_item @appearance ||= Appearance.first end + + def brand_header_logo + image_tag 'logo-white.png' + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 365de3595c..a81e41819b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -58,10 +58,8 @@ module ApplicationHelper Project.find_with_namespace(project_id) end - if project.avatar.present? - image_tag project.avatar.url, options - elsif project.avatar_in_git - image_tag namespace_project_avatar_path(project.namespace, project), options + if project.avatar_url + image_tag project.avatar_url, options else # generated icon project_identicon(project, options) end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 063916a8df..d38b546e1b 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -30,7 +30,7 @@ module EventsHelper end content_tag :li, class: "filter_icon #{active}" do - link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do + link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => 'Filter by ' + tooltip.downcase do icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) end end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb new file mode 100644 index 0000000000..ac37f909ce --- /dev/null +++ b/app/helpers/gitlab_routing_helper.rb @@ -0,0 +1,47 @@ +# Shorter routing method for project and project items +# Since update to rails 4.1.9 we are now allowed to use `/` in project routing +# so we use nested routing for project resources which include project and +# project namespace. To avoid writing long methods every time we define shortcuts for +# some of routing. +# +# For example instead of this: +# +# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request) +# +# We can simply use shortcut: +# +# merge_request_path(merge_request) +# +module GitlabRoutingHelper + def project_path(project, *args) + namespace_project_path(project.namespace, project, *args) + end + + def edit_project_path(project, *args) + edit_namespace_project_path(project.namespace, project, *args) + end + + def issue_path(entity, *args) + namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args) + end + + def merge_request_path(entity, *args) + namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args) + end + + def project_url(project, *args) + namespace_project_url(project.namespace, project, *args) + end + + def edit_project_url(project, *args) + edit_namespace_project_url(project.namespace, project, *args) + end + + def issue_url(entity, *args) + namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args) + end + + def merge_request_url(entity, *args) + namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args) + end +end diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index aae6eff50d..8f841860d1 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -12,7 +12,7 @@ module OauthHelper end def default_providers - [:twitter, :github, :gitlab, :google_oauth2, :ldap] + [:twitter, :github, :gitlab, :bitbucket, :google_oauth2, :ldap] end def enabled_oauth_providers @@ -21,13 +21,15 @@ module OauthHelper def enabled_social_providers enabled_oauth_providers.select do |name| - [:twitter, :gitlab, :github, :google_oauth2, :kerberos,].include?(name.to_sym) + [:twitter, :gitlab, :github, :bitbucket, :google_oauth2, :kerberos].include?(name.to_sym) end end def additional_providers - enabled_oauth_providers.reject do |provider| + enabled_oauth_providers.reject do |provider| provider.to_s.starts_with?('ldap') || provider == :kerberos end end + + extend self end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 4bcd222d08..34265a02f7 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -46,7 +46,7 @@ module ProjectsHelper simple_sanitize(project.group.name), group_path(project.group) ) + ' / ' + link_to(simple_sanitize(project.name), - namespace_project_path(project.namespace, project)) + project_path(project)) end else owner = project.namespace.owner @@ -55,7 +55,7 @@ module ProjectsHelper simple_sanitize(owner.name), user_path(owner) ) + ' / ' + link_to(simple_sanitize(project.name), - namespace_project_path(project.namespace, project)) + project_path(project)) end end end diff --git a/app/models/group.rb b/app/models/group.rb index d1c25e34b6..209345b677 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -26,7 +26,7 @@ class Group < Namespace validate :avatar_type, if: ->(user) { user.avatar_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } - mount_uploader :avatar, AttachmentUploader + mount_uploader :avatar, AvatarUploader after_create :post_create_hook after_destroy :post_destroy_hook diff --git a/app/models/project.rb b/app/models/project.rb index ada9ca9ff7..573c3384bf 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -37,6 +37,8 @@ class Project < ActiveRecord::Base include Gitlab::ShellAdapter include Gitlab::VisibilityLevel include Gitlab::ConfigHelper + include Rails.application.routes.url_helpers + extend Gitlab::ConfigHelper extend Enumerize @@ -48,6 +50,12 @@ class Project < ActiveRecord::Base default_value_for :wall_enabled, false default_value_for :snippets_enabled, gitlab_config_features.snippets + # set last_activity_at to the same as created_at + after_create :set_last_activity_at + def set_last_activity_at + update_column(:last_activity_at, self.created_at) + end + ActsAsTaggableOn.strict_case_match = true acts_as_taggable_on :tags @@ -66,6 +74,7 @@ class Project < ActiveRecord::Base has_one :gitlab_ci_service, dependent: :destroy has_one :campfire_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy + has_one :irker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy @@ -136,7 +145,7 @@ class Project < ActiveRecord::Base validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id validates :import_url, - format: { with: URI::regexp(%w(git http https)), message: 'should be a valid url' }, + format: { with: URI::regexp(%w(ssh git http https)), message: 'should be a valid url' }, if: :import? validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create @@ -144,7 +153,7 @@ class Project < ActiveRecord::Base if: ->(project) { project.avatar && project.avatar_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } - mount_uploader :avatar, AttachmentUploader + mount_uploader :avatar, AvatarUploader # Scopes scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } @@ -416,6 +425,14 @@ class Project < ActiveRecord::Base @avatar_file end + def avatar_url + if avatar.present? + [gitlab_config.url, avatar.url].join + elsif avatar_in_git + [gitlab_config.url, namespace_project_avatar_path(namespace, self)].join + end + end + # For compatibility with old code def code path diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb new file mode 100644 index 0000000000..a0203a5bb1 --- /dev/null +++ b/app/models/project_services/irker_service.rb @@ -0,0 +1,152 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) + +require 'uri' + +class IrkerService < Service + prop_accessor :colorize_messages, :recipients, :channels + validates :recipients, presence: true, if: :activated? + validate :check_recipients_count, if: :activated? + + before_validation :get_channels + after_initialize :initialize_settings + + # Writer for RSpec tests + attr_writer :settings + + def initialize_settings + # See the documentation (doc/project_services/irker.md) for possible values + # here + @settings ||= { + server_ip: 'localhost', + server_port: 6659, + max_channels: 3, + default_irc_uri: nil + } + end + + def title + 'Irker (IRC gateway)' + end + + def description + 'Send IRC messages, on update, to a list of recipients through an Irker '\ + 'gateway.' + end + + def help + msg = 'Recipients have to be specified with a full URI: '\ + 'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\ + 'the channel to be a nickname instead, append ",isnick" to the channel '\ + 'name; if the channel is protected by a secret password, append '\ + '"?key=secretpassword" to the URI.' + + unless @settings[:default_irc].nil? + msg += ' Note that a default IRC URI is provided by this service\'s '\ + "administrator: #{default_irc}. You can thus just give a channel name." + end + msg + end + + def to_param + 'irker' + end + + def execute(push_data) + IrkerWorker.perform_async(project_id, channels, + colorize_messages, push_data, @settings) + end + + def fields + [ + { type: 'textarea', name: 'recipients', + placeholder: 'Recipients/channels separated by whitespaces' }, + { type: 'checkbox', name: 'colorize_messages' }, + ] + end + + private + + def check_recipients_count + return true if recipients.nil? || recipients.empty? + + if recipients.split(/\s+/).count > max_chans + errors.add(:recipients, "are limited to #{max_chans}") + end + end + + def max_chans + @settings[:max_channels] + end + + def get_channels + return true unless :activated? + return true if recipients.nil? || recipients.empty? + + map_recipients + + errors.add(:recipients, 'are all invalid') if channels.empty? + true + end + + def map_recipients + self.channels = recipients.split(/\s+/).map do |recipient| + format_channel default_irc_uri, recipient + end + channels.reject! &:nil? + end + + def default_irc_uri + default_irc = @settings[:default_irc_uri] + if !(default_irc.nil? || default_irc[-1] == '/') + default_irc += '/' + end + default_irc + end + + def format_channel(default_irc, recipient) + cnt = 0 + url = nil + + # Try to parse the chan as a full URI + begin + uri = URI.parse(recipient) + raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0 + rescue URI::InvalidURIError + unless default_irc.nil? + cnt += 1 + recipient = "#{default_irc}#{recipient}" + retry if cnt == 1 + end + else + url = consider_uri uri + end + url + end + + def consider_uri(uri) + # Authorize both irc://domain.com/#chan and irc://domain.com/chan + if uri.is_a?(URI) && uri.scheme[/^ircs?$/] && !uri.path.nil? + # Do not authorize irc://domain.com/ + if uri.fragment.nil? && uri.path.length > 1 + uri.to_s + else + # Authorize irc://domain.com/smthg#chan + # The irker daemon will deal with it by concatenating smthg and + # chan, thus sending messages on #smthgchan + uri.to_s + end + end + end +end diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 297d8bbb5d..c7cbff63fe 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -14,7 +14,7 @@ # class SlackService < Service - prop_accessor :webhook + prop_accessor :webhook, :username, :channel validates :webhook, presence: true, if: :activated? def title @@ -31,7 +31,10 @@ class SlackService < Service def fields [ - { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' } + { type: 'text', name: 'webhook', + placeholder: 'https://hooks.slack.com/services/...' }, + { type: 'text', name: 'username', placeholder: 'username' }, + { type: 'text', name: 'channel', placeholder: '#channel' } ] end @@ -43,7 +46,11 @@ class SlackService < Service project_name: project_name )) - notifier = Slack::Notifier.new(webhook) + opt = {} + opt[:channel] = channel if channel + opt[:username] = username if username + + notifier = Slack::Notifier.new(webhook, opt) notifier.ping(message.pretext, attachments: message.attachments) end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index c4b6ef5d9a..b6932f1c77 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -115,13 +115,13 @@ class TeamcityService < CiService end end - def execute(data) + def execute(push) auth = { username: username, password: password, } - branch = data[:ref] + branch = push[:ref].gsub('refs/heads/', '') self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", body: "Access denied! Please verify you can add deploy keys to this repository.
"") - else :plain job = $("tr#repo_#{@repo_id}") job.attr("id", "project_#{@project.id}") + target_field = job.find(".import-target") + target_field.empty() + target_field.append('#{link_to @project.path_with_namespace, [@project.namespace.becomes(Namespace), @project]}') $("table.import-jobs tbody").prepend(job) job.addClass("active").find(".import-actions").html(" started") diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml new file mode 100644 index 0000000000..bcbbaadf3e --- /dev/null +++ b/app/views/import/bitbucket/status.html.haml @@ -0,0 +1,46 @@ +%h3.page-title + %i.fa.fa-bitbucket + Import projects from Bitbucket + +%p.light + Select projects you want to import. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From Bitbucket + %th To GitLab + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td + = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + %td.job-status + - if project.import_status == 'finished' + %span.cgreen + %i.fa.fa-check + done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} + %td + = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank" + %td.import-target + = "#{repo["owner"]}/#{repo["slug"]}" + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + $ -> + new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}") diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 84d9903fe1..883090a302 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title %i.fa.fa-github - Import repositories from GitHub.com + Import projects from GitHub %p.light Select projects you want to import. @@ -17,20 +17,25 @@ %tbody - @already_added_projects.each do |project| %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td= project.import_source %td - %strong= link_to project.path_with_namespace, project + = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span.cgreen %i.fa.fa-check done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started - else = project.human_import_status_name - @repos.each do |repo| %tr{id: "repo_#{repo.id}"} - %td= repo.full_name + %td + = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank" %td.import-target = repo.full_name %td.import-actions.job-status diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index d1e48dfad2..41ac073eae 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title - %i.fa.fa-github - Import repositories from GitLab.com + %i.fa.fa-heart + Import projects from GitLab.com %p.light Select projects you want to import. @@ -12,25 +12,30 @@ %thead %tr %th From GitLab.com - %th To GitLab private instance + %th To this GitLab instance %th Status %tbody - @already_added_projects.each do |project| %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td= project.import_source %td - %strong= link_to project.path_with_namespace, project + = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span.cgreen %i.fa.fa-check done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started - else = project.human_import_status_name - @repos.each do |repo| %tr{id: "repo_#{repo["id"]}"} - %td= repo["path_with_namespace"] + %td + = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank" %td.import-target = repo["path_with_namespace"] %td.import-actions.job-status diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index 35ed0a717d..ebe24747a0 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -1,6 +1,6 @@ %h3.page-title - %i.fa.fa-gitorious - Import repositories from Gitorious.org + %i.icon-gitorious.icon-gitorious-big + Import projects from Gitorious.org %p.light Select projects you want to import. @@ -11,26 +11,31 @@ %table.table.import-jobs %thead %tr - %th From Gitorious + %th From Gitorious.org %th To GitLab %th Status %tbody - @already_added_projects.each do |project| %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td= project.import_source %td - %strong= link_to project.path_with_namespace, project + = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span.cgreen %i.fa.fa-check done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started - else = project.human_import_status_name - @repos.each do |repo| %tr{id: "repo_#{repo.id}"} - %td= repo.full_name + %td + = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank" %td.import-target = repo.full_name %td.import-actions.job-status diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml index b3b338b55b..2ed51d87ca 100644 --- a/app/views/layouts/_collapse_button.html.haml +++ b/app/views/layouts/_collapse_button.html.haml @@ -1,4 +1,4 @@ - if nav_menu_collapsed? - = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse' + = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close" - else - = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse' + = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close" diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index ec8550b1e6..7e4ea03cbf 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -4,7 +4,7 @@ %div.app_logo - brand_header_logo if brand_item = link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do - %h1 GITLAB + = brand_header_logo %h1.title= title %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} @@ -43,7 +43,7 @@ = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do %i.fa.fa-sign-out %li.hidden-xs - = link_to current_user, class: "profile-pic", id: 'profile-pic' do + = link_to current_user, class: "profile-pic has_bottom_tooltip", id: 'profile-pic', 'data-original-title' => 'Your profile' do = image_tag avatar_icon(current_user.email, 60), alt: 'User activity' = render 'shared/outdated_browser' diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index e912fea2ae..bd6bb3c720 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -2,10 +2,8 @@ .navbar-inner .container %div.app_logo - %span.separator = link_to explore_root_path, class: "home" do - %h1 GITLAB - %span.separator + = brand_header_logo %h1.title= title %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index e8751a6987..ab84e87c30 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", title: "Admin area" - %body{class: "#{app_theme} #{theme_type} admin", :'data-page' => body_data_page} + %body{class: "#{app_theme} admin", :'data-page' => body_data_page} = render "layouts/head_panel", title: link_to("Admin area", admin_root_path) = render 'layouts/page', sidebar: 'layouts/nav/admin' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 49123744ff..6bd8ac4adb 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", title: "Dashboard" - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page } + %body{class: "#{app_theme} application", :'data-page' => body_data_page } = render "layouts/head_panel", title: link_to("Dashboard", root_path) = render 'layouts/page', sidebar: 'layouts/nav/dashboard' diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index e7d875173e..e51fd4cb82 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Error" - %body{class: "#{app_theme} #{theme_type} application"} + %body{class: "#{app_theme} application"} = render "layouts/head_panel", title: "" if current_user .container.navless-container = render "layouts/flash" diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml index 09855b222d..2bd0b8d85c 100644 --- a/app/views/layouts/explore.html.haml +++ b/app/views/layouts/explore.html.haml @@ -2,7 +2,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: page_title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" - if current_user = render "layouts/head_panel", title: link_to(page_title, explore_root_path) diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index fa0ed317ce..f4a6bee15f 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", title: group_head_title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/head_panel", title: link_to(@group.name, group_path(@group)) = render 'layouts/page', sidebar: 'layouts/nav/group' diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 15b489c7d9..d340ab1796 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,8 +1,8 @@ %ul.project-navigation.nav.nav-sidebar - if @project_settings_nav = nav_link do - = link_to namespace_project_path(@project.namespace, @project), title: 'Back to project', class: "" do - %i.fa.fa-angle-left + = link_to project_path(@project), title: 'Back to project', class: "" do + %i.fa.fa-caret-square-o-left %span Back to project @@ -12,7 +12,7 @@ - else = nav_link(path: 'projects#show', html_options: {class: "home"}) do - = link_to namespace_project_path(@project.namespace, @project), title: 'Project', class: 'shortcuts-project' do + = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do %i.fa.fa-dashboard %span Project @@ -89,7 +89,7 @@ - if project_nav_tab? :settings = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do - = link_to edit_namespace_project_path(@project.namespace, @project), title: 'Settings', class: "stat-tab tab no-highlight" do + = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do %i.fa.fa-cogs %span Settings diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml index a3b55542bf..4d0278251a 100644 --- a/app/views/layouts/navless.html.haml +++ b/app/views/layouts/navless.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" = render "layouts/head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title .container.navless-container diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 19d6efed78..2b5be7fc37 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", title: "Profile" - %body{class: "#{app_theme} #{theme_type} profile", :'data-page' => body_data_page} + %body{class: "#{app_theme} profile", :'data-page' => body_data_page} = render "layouts/head_panel", title: link_to("Profile", profile_path) = render 'layouts/page', sidebar: 'layouts/nav/profile' diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index d2c9c2a991..0a0039dec1 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace - %body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } + %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" - @project_settings_nav = true diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index c44a40c9c1..dde0964f47 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: project_head_title - %body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } + %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml index 4b69329b8f..b9b1d03e08 100644 --- a/app/views/layouts/public_group.html.haml +++ b/app/views/layouts/public_group.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: group_head_title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/public_head_panel", title: link_to(@group.name, group_path(@group)) = render 'layouts/page', sidebar: 'layouts/nav/group' diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml index 027e9a5313..04fa7c84e7 100644 --- a/app/views/layouts/public_projects.html.haml +++ b/app/views/layouts/public_projects.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/public_head_panel", title: project_title(@project) = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml index 3538a8b169..71c16bd168 100644 --- a/app/views/layouts/public_users.html.haml +++ b/app/views/layouts/public_users.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @title - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/public_head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title = render 'layouts/page' diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml index 177e2073a0..f9d8db06e1 100644 --- a/app/views/layouts/search.html.haml +++ b/app/views/layouts/search.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Search" - %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" = render "layouts/head_panel", title: link_to("Search", search_path) .container.navless-container diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 53a50f6796..f124637c07 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,5 +1,5 @@ %h3.page-title - Account settings + Account Settings %p.light You can change your username and private token here. - if current_user.ldap_user? @@ -10,7 +10,7 @@ .account-page %fieldset.update-token %legend - Private token + Reset Private token %div = form_for @user, url: reset_private_token_profile_path, method: :put do |f| .data @@ -25,7 +25,7 @@ - if current_user.private_token = text_field_tag "token", current_user.private_token, class: "form-control" %div - = f.submit 'Reset', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token" + = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token" - else %span You don`t have one yet. Click generate to fix it. = f.submit 'Generate', class: "btn success btn-build-token" @@ -43,7 +43,7 @@ - if show_profile_username_tab? %fieldset.update-username %legend - Username + Change Username = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| %p Changing your username will change path to all personal projects! diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index 4b5817e10b..c8c522e981 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -1,5 +1,7 @@ %h3.page-title - OAuth2 + Application Settings +%p.light + OAuth2 protocol settings below. %fieldset.oauth-applications %legend Your applications diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml index 0d8075b7d4..8d09595fd4 100644 --- a/app/views/profiles/design.html.haml +++ b/app/views/profiles/design.html.haml @@ -1,7 +1,7 @@ %h3.page-title - My appearance settings + Design Settings %p.light - Appearance settings saved to your profile and available across all devices + Appearance settings will be saved to your profile and made available across all devices. %hr = form_for @user, url: profile_path, remote: true, method: :put do |f| diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 0b30e77233..3bbad6fdf7 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,5 +1,5 @@ %h3.page-title - My email addresses + Email Settings %p.light Your %b Primary Email @@ -34,4 +34,4 @@ .col-sm-10 = f.text_field :email, class: 'form-control' .form-actions - = f.submit 'Add', class: 'btn btn-create' + = f.submit 'Add email address', class: 'btn btn-create' diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml index e9ffca8faf..daf76636ff 100644 --- a/app/views/profiles/groups/index.html.haml +++ b/app/views/profiles/groups/index.html.haml @@ -1,12 +1,12 @@ %h3.page-title - Group membership + Group Membership - if current_user.can_create_group? %span.pull-right = link_to new_group_path, class: "btn btn-new" do %i.fa.fa-plus New Group %p.light - Group members have access to all a group's projects + Group members have access to all group projects. %hr .panel.panel-default .panel-heading diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml index 3951c47b5f..9cafe03b8b 100644 --- a/app/views/profiles/history.html.haml +++ b/app/views/profiles/history.html.haml @@ -1,7 +1,7 @@ %h3.page-title - Account history + My Account History %p.light - All events created by your account are listed here + All events created by your account are listed below. %hr .profile_history = render @events diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index c83c73ffcf..965d5e032f 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,12 +1,12 @@ %h3.page-title - My SSH keys (#{@keys.count}) + SSH Keys Settings .pull-right = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" %p.light - SSH keys allow you to establish a secure connection between your computer and GitLab + My SSH keys: #{@keys.count} %br Before you can add an SSH key you need to - = link_to "generate it", help_page_path("ssh", "README") + = link_to "generate it.", help_page_path("ssh", "README") %hr = render 'key_table' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 28bc5a426a..e3cd323927 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,10 +1,9 @@ %h3.page-title - Notifications settings + Notifications Settings %p.light These are your global notification settings. %hr - = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f| -if @user.errors.any? %div.alert.alert-danger @@ -60,7 +59,7 @@ %p You can also specify notification level per group or per project. %br - By default all projects and groups uses notification level set above. + By default, all projects and groups will use the notification level set above. %h4 Groups: %ul.bordered-list - @group_members.each do |users_group| @@ -69,7 +68,7 @@ .col-md-6 %p - To specify notification level per project of a group you belong to, + To specify the notification level per project of a group you belong to, %br you need to be a member of the project itself, not only its group. %h4 Projects: diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 6b19db4eb5..3b1ebbfaf5 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,4 +1,4 @@ -%h3.page-title Password +%h3.page-title Password Settings %p.light - if @user.password_automatically_set? Set your password. @@ -12,7 +12,7 @@ - unless @user.password_automatically_set? You must provide current password in order to change it. %br - After a successful password update you will be redirected to login page where you should login with your new password + After a successful password update, you will be redirected to the login page where you can log in with your new password. -if @user.errors.any? .alert.alert-danger %ul diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 640104fdad..b2808c46c0 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,7 +1,7 @@ %h3.page-title - Profile settings + Profile Settings %p.light - This information appears on your profile. + This information will appear on your profile. - if current_user.ldap_user? Some options are unavailable for LDAP accounts %hr diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml new file mode 100644 index 0000000000..5c52f91927 --- /dev/null +++ b/app/views/projects/_bitbucket_import_modal.html.haml @@ -0,0 +1,13 @@ +%div#bitbucket_import_modal.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 Import projects from Bitbucket + .modal-body + To enable importing projects from Bitbucket, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/butbucket.md'}. \ No newline at end of file diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml index 99325e6611..e88a0f7d68 100644 --- a/app/views/projects/_github_import_modal.html.haml +++ b/app/views/projects/_github_import_modal.html.haml @@ -3,7 +3,11 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 GitHub OAuth import + %h3 Import projects from GitHub .modal-body - You need to setup integration with GitHub first. - = link_to 'How to setup integration with GitHub', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md' \ No newline at end of file + To enable importing projects from GitHub, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'}. \ No newline at end of file diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml index e7503f023b..52212b6ae0 100644 --- a/app/views/projects/_gitlab_import_modal.html.haml +++ b/app/views/projects/_gitlab_import_modal.html.haml @@ -3,7 +3,11 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 GitLab OAuth import + %h3 Import projects from GitLab.com .modal-body - You need to setup integration with GitLab first. - = link_to 'How to setup integration with GitLab', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md' \ No newline at end of file + To enable importing projects from GitLab.com, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'}. \ No newline at end of file diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index a7d5a52584..bfacab5e48 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -23,7 +23,7 @@ Parsed with #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}. .pull-right - Attach images (JPG, PNG, GIF) by dragging & dropping + Attach files by dragging & dropping or #{link_to 'selecting them', '#', class: 'markdown-selector' }. .clearfix diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml index 29e1e48764..411e6ca354 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -1,6 +1,6 @@ %ul.project-settings-nav.sidebar-subnav = nav_link(path: 'projects#edit') do - = link_to edit_namespace_project_path(@project.namespace, @project), title: 'Project', class: "stat-tab tab " do + = link_to edit_project_path(@project), title: 'Project', class: "stat-tab tab " do %i.fa.fa-pencil-square-o %span Project diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 6884ad1f2f..1f61a0b940 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -14,6 +14,13 @@ = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}" + + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @ref, class: "form-control" + = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 45865d552a..d78a01f642 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -4,6 +4,13 @@ = render 'projects/blob/editor', ref: @ref = render 'shared/commit_message_container', params: params, placeholder: 'Add new file' + + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @ref, class: "form-control" + = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index 52da85cbdf..230e164f24 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -13,7 +13,7 @@ = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right" - = key_project = deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first + - key_project = deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first = link_to namespace_project_deploy_key_path(key_project.namespace, key_project, deploy_key) do %i.fa.fa-key %strong= deploy_key.title diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index 5725c84600..c9a6b3ebd9 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -10,8 +10,8 @@ = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-small" = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-warning btn-small" - elsif @merge_request && @merge_request.persisted? - = link_to "Plain diff", namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :diff), class: "btn btn-warning btn-small" - = link_to "Email patch", namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :patch), class: "btn btn-warning btn-small" + = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-warning btn-small" + = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-small" %p To preserve performance only %strong #{allowed_diff_size} of #{diffs.size} diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 2bd3d8a73e..fc3e35640d 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -1,9 +1,9 @@ - content_for :note_actions do - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen Issue', namespace_project_issue_path(@project.namespace, @project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue' + = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue' - else - = link_to 'Close Issue', namespace_project_issue_path(@project.namespace, @project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" + = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" .row %section.col-md-9 .participants diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 679e84c366..7d7217eb2a 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,5 +1,5 @@ %div.issue-form-holder - %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" + %h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}" %hr = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f| @@ -11,4 +11,4 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_namespace_project_path @project.namespace, @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 8af8da1d13..01e2133e28 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,11 +1,11 @@ -%li{ id: dom_id(issue), class: issue_css_classes(issue), url: namespace_project_issue_path(issue.project.namespace, issue.project, issue) } +%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) } - if controller.controller_name == 'issues' .issue-check = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) .issue-title %span.str-truncated - = link_to_gfm issue.title, namespace_project_issue_path(issue.project.namespace, issue.project, issue), class: "row_title" + = link_to_gfm issue.title, issue_path(issue), class: "row_title" .pull-right.light - if issue.closed? %span @@ -41,9 +41,9 @@ .issue-actions - if can? current_user, :modify_issue, issue - if issue.closed? - = link_to 'Reopen', namespace_project_issue_path(issue.project.namespace, issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue btn-reopen", remote: true + = link_to 'Reopen', issue_path(issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue btn-reopen", remote: true - else - = link_to 'Close', namespace_project_issue_path(issue.project.namespace, issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true + = link_to 'Close', issue_path(issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true = link_to edit_namespace_project_issue_path(issue.project.namespace, issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do %i.fa.fa-pencil-square-o Edit diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 6849a15e7e..bd28d8a1db 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -17,9 +17,9 @@ New Issue - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen', namespace_project_issue_path(@project.namespace, @project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" + = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" - else - = link_to 'Close', namespace_project_issue_path(@project.namespace, @project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" + = link_to 'Close', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: "btn btn-grouped issuable-edit" do %i.fa.fa-pencil-square-o diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 2df35aac02..79a093dc77 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -1,9 +1,9 @@ - content_for :note_actions do - if can?(current_user, :modify_merge_request, @merge_request) - if @merge_request.open? - = link_to 'Close', namespace_project_merge_request_path(@project.namespace, @project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" + = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" - if @merge_request.closed? - = link_to 'Reopen', namespace_project_merge_request_path(@project.namespace, @project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" .row %section.col-md-9 diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 893c7daf3c..1c7160bce5 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -9,4 +9,4 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_namespace_project_path @project.namespace, @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index d94636712b..1eba1a96b7 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -1,7 +1,7 @@ %li{ class: mr_css_classes(merge_request) } .merge-request-title %span.str-truncated - = link_to_gfm merge_request.title, namespace_project_merge_request_path(merge_request.target_project.namespace, merge_request.target_project, merge_request), class: "row_title" + = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" .pull-right.light - if merge_request.merged? %span diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 75d65e6649..73eccfa556 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -27,7 +27,7 @@ Parsed with #{link_to 'Gitlab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}. .pull-right - Attach images (JPG, PNG, GIF) by dragging & dropping + Attach files by dragging & dropping or #{link_to 'selecting them', '#', class: 'markdown-selector'}. .clearfix @@ -113,10 +113,11 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_namespace_project_path @project.namespace, @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; :javascript var merge_request merge_request = new MergeRequest({ action: 'commits' }); + diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index a53aed2f38..ca4ceecb22 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,4 @@ -.merge-request{'data-url' => namespace_project_merge_request_path(@project.namespace, @project, @merge_request)} +.merge-request{'data-url' => merge_request_path(@merge_request)} .merge-request-details = render "projects/merge_requests/show/mr_title" %hr @@ -28,8 +28,8 @@ Download as %span.caret %ul.dropdown-menu - %li= link_to "Email Patches", namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :patch) - %li= link_to "Plain Diff", namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :diff) + %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) + %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/show/state_widget" @@ -37,12 +37,12 @@ - if @commits.present? %ul.nav.nav-tabs.merge-request-tabs %li.notes-tab{data: {action: 'notes'}} - = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request) do + = link_to merge_request_path(@merge_request) do %i.fa.fa-comments Discussion %span.badge= @merge_request.mr_and_commit_notes.count %li.commits-tab{data: {action: 'commits'}} - = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), title: 'Commits' do + = link_to merge_request_path(@merge_request), title: 'Commits' do %i.fa.fa-history Commits %span.badge= @commits.size diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index eb1640891e..cfef1d5e4c 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -8,5 +8,5 @@ Changes view for this comparison is extremely large. %p You can - = link_to "download it", namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, format: :diff), class: "vlink" + = link_to "download it", merge_request_path(@merge_request, format: :diff), class: "vlink" instead. diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 4c230953cb..46e92a9c55 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -14,9 +14,9 @@ .issue-btn-group.pull-right - if can?(current_user, :modify_merge_request, @merge_request) - if @merge_request.open? - = link_to 'Close', namespace_project_merge_request_path(@project.namespace, @project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do %i.fa.fa-pencil-square-o Edit - if @merge_request.closed? - = link_to 'Reopen', namespace_project_merge_request_path(@project.namespace, @project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index e9c2a73bd3..95b7070ce5 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -25,7 +25,7 @@ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' .hint .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .clearfix .error-alert .col-md-6 @@ -51,4 +51,4 @@ onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); - window.project_image_path_upload = "#{upload_image_namespace_project_path @project.namespace, @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml index 36463371f4..26c83841a2 100644 --- a/app/views/projects/milestones/_issue.html.haml +++ b/app/views/projects/milestones/_issue.html.haml @@ -1,4 +1,4 @@ -%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => namespace_project_issue_path(@project.namespace, @project, issue) } +%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) } %span.str-truncated = link_to [@project.namespace.becomes(Namespace), @project, issue] do %span.cgray ##{issue.iid} diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index 3180c1d91b..46f2df1b18 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -1,4 +1,4 @@ -%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => namespace_project_merge_request_path(@project.namespace, @project, merge_request) } +%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) } %span.str-truncated = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do %span.cgray ##{merge_request.iid} diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 5216f30811..025c4fd550 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -53,6 +53,19 @@ Import projects from GitHub = render 'github_import_modal' + .project-import.form-group + .col-sm-2 + .col-sm-10 + - if bitbucket_import_enabled? + = link_to status_import_bitbucket_path do + %i.fa.fa-bitbucket + Import projects from Bitbucket + - else + = link_to '#', class: 'how_to_import_link light' do + %i.fa.fa-bitbucket + Import projects from Bitbucket + = render 'bitbucket_import_modal' + - unless request.host == 'gitlab.com' .project-import.form-group .col-sm-2 @@ -71,7 +84,7 @@ .col-sm-2 .col-sm-10 = link_to new_import_gitorious_path do - %i.fa.fa-heart + %i.icon-gitorious.icon-gitorious-small Import projects from Gitorious.org %hr.prepend-botton-10 diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml index e8fd90efd1..720957e833 100644 --- a/app/views/projects/no_repo.html.haml +++ b/app/views/projects/no_repo.html.haml @@ -19,4 +19,4 @@ - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to 'Remove project', namespace_project_path(@project.namespace, @project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" + = link_to 'Remove project', project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index 602acd6d01..b51ca79541 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -6,17 +6,9 @@ .comment-hints.clearfix .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. .note-form-actions .buttons = f.submit 'Save Comment', class: "btn btn-primary btn-save btn-grouped js-comment-button" - = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" - - .note-form-option.hidden-xs - %a.choose-btn.btn.js-choose-note-attachment-button - %i.fa.fa-paperclip - %span Choose File ... - - %span.file_name.js-attachment-filename - = f.file_field :attachment, class: "js-note-attachment-input hidden" + = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" \ No newline at end of file diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index e1f2754f0d..4476337cb1 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -11,7 +11,7 @@ .comment-hints.clearfix .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. .note-form-actions @@ -20,13 +20,5 @@ = yield(:note_actions) %a.btn.grouped.js-close-discussion-note-form Cancel - .note-form-option.hidden-xs - %a.choose-btn.btn.js-choose-note-attachment-button - %i.fa.fa-paperclip - %span Choose File ... - - %span.file_name.js-attachment-filename - = f.file_field :attachment, class: "js-note-attachment-input hidden" - :javascript - window.project_image_path_upload = "#{upload_image_namespace_project_path @project.namespace, @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 0be3ded6df..f3d00a6f06 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -57,10 +57,10 @@ - if note.attachment.url .note-attachment - if note.attachment.image? - = link_to note.attachment.secure_url, target: '_blank' do - = image_tag note.attachment.secure_url, class: 'note-image-attach' + = link_to note.attachment.url, target: '_blank' do + = image_tag note.attachment.url, class: 'note-image-attach' .attachment - = link_to note.attachment.secure_url, target: "_blank" do + = link_to note.attachment.url, target: "_blank" do %i.fa.fa-paperclip = note.attachment_identifier = link_to delete_attachment_namespace_project_note_path(@project.namespace, @project, note), diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml index d615d12865..0d3ccb6bb8 100644 --- a/app/views/projects/services/index.html.haml +++ b/app/views/projects/services/index.html.haml @@ -6,7 +6,7 @@ %tr %th %th Service - %th Desription + %th Description %th Last edit - @services.sort_by(&:title).each do |service| %tr diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 0d52f9ecbb..9fbfa0b1ae 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -26,7 +26,7 @@ = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' .col-sm-12.hint .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .clearfix .error-alert @@ -43,5 +43,4 @@ = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel" :javascript - window.project_image_path_upload = "#{upload_image_namespace_project_path @project.namespace, @project}"; - + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb new file mode 100644 index 0000000000..613bae351d --- /dev/null +++ b/app/workers/irker_worker.rb @@ -0,0 +1,169 @@ +require 'json' +require 'socket' + +class IrkerWorker + include Sidekiq::Worker + + def perform(project_id, chans, colors, push_data, settings) + project = Project.find(project_id) + + # Get config parameters + return false unless init_perform settings, chans, colors + + repo_name = push_data['repository']['name'] + committer = push_data['user_name'] + branch = push_data['ref'].gsub(%r'refs/[^/]*/', '') + + if @colors + repo_name = "\x0304#{repo_name}\x0f" + branch = "\x0305#{branch}\x0f" + end + + # Firsts messages are for branch creation/deletion + send_branch_updates push_data, project, repo_name, committer, branch + + # Next messages are for commits + send_commits push_data, project, repo_name, committer, branch + + close_connection + true + end + + private + + def init_perform(set, chans, colors) + @colors = colors + @channels = chans + start_connection set['server_ip'], set['server_port'] + end + + def start_connection(irker_server, irker_port) + begin + @socket = TCPSocket.new irker_server, irker_port + rescue Errno::ECONNREFUSED => e + logger.fatal "Can't connect to Irker daemon: #{e}" + return false + end + true + end + + def sendtoirker(privmsg) + to_send = { to: @channels, privmsg: privmsg } + @socket.puts JSON.dump(to_send) + end + + def close_connection + @socket.close + end + + def send_branch_updates(push_data, project, repo_name, committer, branch) + if push_data['before'] =~ /^000000/ + send_new_branch project, repo_name, committer, branch + elsif push_data['after'] =~ /^000000/ + send_del_branch repo_name, committer, branch + end + end + + def send_new_branch(project, repo_name, committer, branch) + repo_path = project.path_with_namespace + newbranch = "#{Gitlab.config.gitlab.url}/#{repo_path}/branches" + newbranch = "\x0302\x1f#{newbranch}\x0f" if @colors + + privmsg = "[#{repo_name}] #{committer} has created a new branch " + privmsg += "#{branch}: #{newbranch}" + sendtoirker privmsg + end + + def send_del_branch(repo_name, committer, branch) + privmsg = "[#{repo_name}] #{committer} has deleted the branch #{branch}" + sendtoirker privmsg + end + + def send_commits(push_data, project, repo_name, committer, branch) + return if push_data['total_commits_count'] == 0 + + # Next message is for number of commit pushed, if any + if push_data['before'] =~ /^000000/ + # Tweak on push_data["before"] in order to have a nice compare URL + push_data['before'] = before_on_new_branch push_data, project + end + + send_commits_count(push_data, project, repo_name, committer, branch) + + # One message per commit, limited by 3 messages (same limit as the + # github irc hook) + commits = push_data['commits'].first(3) + commits.each do |hook_attrs| + send_one_commit project, hook_attrs, repo_name, branch + end + end + + def before_on_new_branch(push_data, project) + commit = commit_from_id project, push_data['commits'][0]['id'] + parents = commit.parents + # Return old value if there's no new one + return push_data['before'] if parents.empty? + # Or return the first parent-commit + parents[0].id + end + + def send_commits_count(data, project, repo, committer, branch) + url = compare_url data, project.path_with_namespace + commits = colorize_commits data['total_commits_count'] + + new_commits = 'new commit' + new_commits += 's' if data['total_commits_count'] > 1 + + sendtoirker "[#{repo}] #{committer} pushed #{commits} #{new_commits} " \ + "to #{branch}: #{url}" + end + + def compare_url(data, repo_path) + sha1 = Commit::truncate_sha(data['before']) + sha2 = Commit::truncate_sha(data['after']) + compare_url = "#{Gitlab.config.gitlab.url}/#{repo_path}/compare" + compare_url += "/#{sha1}...#{sha2}" + colorize_url compare_url + end + + def send_one_commit(project, hook_attrs, repo_name, branch) + commit = commit_from_id project, hook_attrs['id'] + sha = colorize_sha Commit::truncate_sha(hook_attrs['id']) + author = hook_attrs['author']['name'] + files = colorize_nb_files(files_count commit) + title = commit.title + + sendtoirker "#{repo_name}/#{branch} #{sha} #{author} (#{files}): #{title}" + end + + def commit_from_id(project, id) + commit = Gitlab::Git::Commit.find(project.repository, id) + Commit.new(commit) + end + + def files_count(commit) + files = "#{commit.diffs.count} file" + files += 's' if commit.diffs.count > 1 + files + end + + def colorize_sha(sha) + sha = "\x0314#{sha}\x0f" if @colors + sha + end + + def colorize_nb_files(nb_files) + nb_files = "\x0312#{nb_files}\x0f" if @colors + nb_files + end + + def colorize_url(url) + url = "\x0302\x1f#{url}\x0f" if @colors + url + end + + def colorize_commits(commits) + commits = "\x02#{commits}\x0f" if @colors + commits + end +end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 5f9970d379..437640d230 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -6,25 +6,27 @@ class RepositoryImportWorker def perform(project_id) project = Project.find(project_id) - result = gitlab_shell.send(:import_repository, + + import_result = gitlab_shell.send(:import_repository, project.path_with_namespace, project.import_url) + return project.import_fail unless import_result - result_of_data_import = if project.import_type == 'github' - Gitlab::GithubImport::Importer.new(project).execute - elsif project.import_type == 'gitlab' - Gitlab::GitlabImport::Importer.new(project).execute - else - true - end + data_import_result = if project.import_type == 'github' + Gitlab::GithubImport::Importer.new(project).execute + elsif project.import_type == 'gitlab' + Gitlab::GitlabImport::Importer.new(project).execute + elsif project.import_type == 'bitbucket' + Gitlab::BitbucketImport::Importer.new(project).execute + else + true + end + return project.import_fail unless data_import_result - if result && result_of_data_import - project.import_finish - project.save - project.satellite.create unless project.satellite.exists? - project.update_repository_size - else - project.import_fail - end + project.import_finish + project.save + project.satellite.create unless project.satellite.exists? + project.update_repository_size + Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' end end diff --git a/config/environments/production.rb b/config/environments/production.rb index 78bf543402..3316ece387 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -11,8 +11,9 @@ Gitlab::Application.configure do # Disable Rails's static asset server (Apache or nginx will already do this) config.serve_static_assets = false - # Compress JavaScripts and CSS - config.assets.compress = true + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass # Don't fallback to assets pipeline if a precompiled asset is missed config.assets.compile = true @@ -74,7 +75,6 @@ Gitlab::Application.configure do config.action_mailer.raise_delivery_errors = true config.eager_load = true - config.assets.js_compressor = :uglifier config.allow_concurrency = false end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 854c916f00..42a617b628 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -241,21 +241,23 @@ production: &base # arguments, followed by optional 'args' which can be either a hash or an array. # Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html providers: - # - { name: 'google_oauth2', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'google_oauth2', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # args: { access_type: 'offline', approval_prompt: '' } } - # - { name: 'twitter', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET'} - # - { name: 'gitlab', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'twitter', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET'} + # - { name: 'gitlab', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # args: { scope: 'api' } } - # - { name: 'github', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'github', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # url: "https://github.com/", # args: { scope: 'user:email' } } - # - { name: 'gitlab', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'gitlab', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # args: { scope: 'api' } } + # - { name: 'bitbucket', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET'} diff --git a/config/initializers/public_key.rb b/config/initializers/public_key.rb new file mode 100644 index 0000000000..75d74e3625 --- /dev/null +++ b/config/initializers/public_key.rb @@ -0,0 +1,2 @@ +path = File.expand_path("~/.ssh/id_rsa.pub") +Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path) diff --git a/config/routes.rb b/config/routes.rb index f8222dc166..c505c8d335 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,12 @@ Gitlab::Application.routes.draw do get :jobs end + resource :bitbucket, only: [:create, :new], controller: :bitbucket do + get :status + get :callback + get :jobs + end + resource :gitorious, only: [:create, :new], controller: :gitorious do get :status get :callback @@ -92,11 +98,16 @@ Gitlab::Application.routes.draw do constraints: { model: /appearance/, mounted_as: /logo|dark_logo|light_logo/, filename: /.+/ } # Project markdown uploads - get ":namespace_id/:id/:secret/:filename", + get ":namespace_id/:project_id/:secret/:filename", to: "projects/uploads#show", - constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, id: /[a-zA-Z.0-9_\-]+/, filename: /.+/ } + constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /.+/ } end + # Redirect old note attachments path to new uploads path. + get "files/note/:id/:filename", + to: redirect("uploads/note/attachment/%{id}/%{filename}"), + constraints: { filename: /.+/ } + # # Explore area # @@ -116,11 +127,6 @@ Gitlab::Application.routes.draw do get 'public' => 'explore/projects#index' get 'public/projects' => 'explore/projects#index' - # - # Attachments serving - # - get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ } - # # Admin Area # @@ -282,7 +288,6 @@ Gitlab::Application.routes.draw do put :transfer post :archive post :unarchive - post :upload_image post :toggle_star post :markdown_preview get :autocomplete_sources @@ -479,6 +484,12 @@ Gitlab::Application.routes.draw do delete :delete_attachment end end + + resources :uploads, only: [:create] do + collection do + get ":secret/:filename", action: :show, as: :show, constraints: { filename: /.+/ } + end + end end get "/audit_events" => "audit_events#project_log" diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb index a8e07033a5..5836cd6b8d 100644 --- a/db/migrate/20141006143943_move_slack_service_to_webhook.rb +++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb @@ -10,7 +10,7 @@ class MoveSlackServiceToWebhook < ActiveRecord::Migration slack_service.properties.delete('subdomain') # Room is configured on the Slack side slack_service.properties.delete('room') - slack_service.save + slack_service.save(validate: false) end end end diff --git a/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb new file mode 100644 index 0000000000..23ac1b399e --- /dev/null +++ b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb @@ -0,0 +1,6 @@ +class AddBitbucketAccessTokenAndSecretToUser < ActiveRecord::Migration + def change + add_column :users, :bitbucket_access_token, :string + add_column :users, :bitbucket_access_token_secret, :string + end +end diff --git a/db/migrate/20150223022001_set_missing_last_activity_at.rb b/db/migrate/20150223022001_set_missing_last_activity_at.rb new file mode 100644 index 0000000000..3f6d4d8347 --- /dev/null +++ b/db/migrate/20150223022001_set_missing_last_activity_at.rb @@ -0,0 +1,8 @@ +class SetMissingLastActivityAt < ActiveRecord::Migration + def up + execute "UPDATE projects SET last_activity_at = updated_at WHERE last_activity_at IS NULL" + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index 44ef85518c..2352ec2fe5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -399,7 +399,7 @@ ActiveRecord::Schema.define(version: 20150225214822) do t.boolean "merge_requests_rebase_enabled", default: false t.string "import_type" t.string "import_source" - t.boolean "merge_requests_rebase_default", default: true + t.boolean "merge_requests_rebase_default", default: false end add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree @@ -470,12 +470,12 @@ ActiveRecord::Schema.define(version: 20150225214822) do end create_table "users", force: true do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -483,22 +483,22 @@ ActiveRecord::Schema.define(version: 20150225214822) do t.datetime "created_at" t.datetime "updated_at" t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", default: 1, null: false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", default: 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" @@ -507,14 +507,16 @@ ActiveRecord::Schema.define(version: 20150225214822) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false t.datetime "admin_email_unsubscribed_at" t.string "github_access_token" t.string "gitlab_access_token" t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "bitbucket_access_token" + t.string "bitbucket_access_token_secret" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/doc/api/issues.md b/doc/api/issues.md index 5a2f6a4c22..a7dd8b74c3 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -208,7 +208,7 @@ If an error occurs, an error number and a message explaining the reason is retur ## Delete existing issue (**Deprecated**) -The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `closed` set to 1. +The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `state_event` set to `close`. ``` DELETE /projects/:id/issues/:issue_id diff --git a/doc/api/projects.md b/doc/api/projects.md index d5c25f2a6b..d842245bcc 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -68,7 +68,8 @@ Parameters: "path": "diaspora", "updated_at": "2013-09-30T13: 46: 02Z" }, - "archived": false + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png" }, { "id": 6, @@ -103,7 +104,8 @@ Parameters: "path": "brightbox", "updated_at": "2013-09-30T13:46:02Z" }, - "archived": false + "archived": false, + "avatar_url": null } ] ``` @@ -195,7 +197,8 @@ Parameters: "notification_level": 3 } }, - "archived": false + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png" } ``` diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 8acf85d21c..3316745380 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -15,24 +15,21 @@ Parameters: ```json [ { - "name": "v1.0.0", "commit": { - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "parents": [], - "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d", - "message": "Initial commit", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "Jack Smith", - "email": "jack@example.com" - }, + "author_name": "John Smith", + "author_email": "john@example.com", "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00" + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] }, - "protected": null + "name": "v1.0.0", + "message": null } ] ``` @@ -53,23 +50,23 @@ Parameters: - `message` (optional) - Creates annotated tag. ```json -[ - { - "name": "v1.0.0", - "message": "Release 1.0.0", - "commit": { - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "parents": [], - "message": "Initial commit", - "authored_date": "2012-05-28T04:42:42-07:00", - "author_name": "John Smith", - "author email": "john@example.com", - "committer_name": "Jack Smith", - "committed_date": "2012-05-28T04:42:42-07:00", - "committer_email": "jack@example.com" - }, - } -] +{ + "commit": { + "author_name": "John Smith", + "author_email": "john@example.com", + "authored_date": "2012-05-28T04:42:42-07:00", + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] + }, + "name": "v1.0.0", + "message": null +} ``` The message will be `nil` when creating a lightweight tag otherwise it will contain the annotation. diff --git a/doc/api/users.md b/doc/api/users.md index 71fa62bdd6..a8b7685b50 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -170,6 +170,7 @@ Parameters: - `bio` (optional) - User's biography - `admin` (optional) - User is admin - true or false (default) - `can_create_group` (optional) - User can create groups - true or false +- `confirm` (optional) - Require confirmation - true (default) or false ## User modification diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md index 42f17e1953..821027f43f 100644 --- a/doc/development/shell_commands.md +++ b/doc/development/shell_commands.md @@ -139,6 +139,11 @@ path = File.join(repo_path, user_input) File.read(path) ``` +If you have to use user input a relative path, prefix `./` to the path. + +Prefixing user-supplied paths also offers extra protection against paths +starting with `-` (see the discussion about using `--` above). + ## Guard against path traversal Path traversal is a security where the program (GitLab) tries to restrict user diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md new file mode 100644 index 0000000000..cc6389f5aa --- /dev/null +++ b/doc/integration/bitbucket.md @@ -0,0 +1,121 @@ +# Integrate your server with Bitbucket + +Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account. + +To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket. +Bitbucket will generate an application ID and secret key for you to use. + +1. Sign in to Bitbucket. + +1. Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you. + +1. Select "OAuth" in the left menu. + +1. Select "Add consumer". + +1. Provide the required details. + - Name: This can be anything. Consider something like "\