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) -> - "![" + str.alt + "](" + str.url + ")" + 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) -> - "![" + str.alt + "](" + str.url + ")" + 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: ""\ diff --git a/app/models/repository.rb b/app/models/repository.rb index 4e45a6723b..bbf35f04bb 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -238,7 +238,7 @@ class Repository end def last_commit_for_path(sha, path) - args = %W(git rev-list --max-count 1 #{sha} -- #{path}) + args = %W(git rev-list --max-count=1 #{sha} -- #{path}) sha = Gitlab::Popen.popen(args, path_to_repo).first.strip commit(sha) end diff --git a/app/models/service.rb b/app/models/service.rb index 111d9f3680..adaff1c54f 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -118,6 +118,7 @@ class Service < ActiveRecord::Base jira redmine custom_issue_tracker + irker ) end diff --git a/app/models/user.rb b/app/models/user.rb index 4787b4b503..f766ea1100 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -46,6 +46,7 @@ # github_access_token :string(255) # notification_email :string(255) # password_automatically_set :boolean default(FALSE) +# bitbucket_access_token :string(255) # require 'carrierwave/orm/activerecord' @@ -178,7 +179,7 @@ class User < ActiveRecord::Base end end - mount_uploader :avatar, AttachmentUploader + mount_uploader :avatar, AvatarUploader # Scopes scope :admins, -> { where(admin: true) } @@ -639,9 +640,11 @@ class User < ActiveRecord::Base def contributed_projects_ids Event.where(author_id: self). where("created_at > ?", Time.now - 1.year). - code_push. + where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)", + pushed: Event::PUSHED, created: Event::CREATED). reorder(project_id: :desc). - select('DISTINCT(project_id)'). - map(&:project_id) + select(:project_id). + uniq + .map(&:project_id) end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 0a0cab85aa..9d9408b199 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -42,7 +42,8 @@ module Files created_successfully = new_file_action.commit!( params[:content], params[:commit_message], - params[:encoding] + params[:encoding], + params[:new_branch] ) if created_successfully diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 28badf1413..b8d38d2904 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -27,7 +27,8 @@ module Files edit_file_action.commit!( params[:content], params[:commit_message], - params[:encoding] + params[:encoding], + params[:new_branch] ) success diff --git a/app/services/projects/image_service.rb b/app/services/projects/image_service.rb deleted file mode 100644 index 7ca7e82c4a..0000000000 --- a/app/services/projects/image_service.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Projects - class ImageService < BaseService - include Rails.application.routes.url_helpers - def initialize(repository, params, root_url) - @repository, @params, @root_url = repository, params.dup, root_url - end - - def execute - uploader = FileUploader.new('uploads', upload_path, accepted_images) - image = @params['markdown_img'] - - if image && correct_mime_type?(image) - alt = image.original_filename - uploader.store!(image) - link = { - 'alt' => File.basename(alt, '.*'), - 'url' => File.join(@root_url, uploader.url) - } - else - link = nil - end - end - - protected - - 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 correct_mime_type?(image) - accepted_images.map{ |format| image.content_type.include? format }.any? - end - end -end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 0be50fed7c..f6f9aceef9 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -35,15 +35,21 @@ module Projects end def sorted(users) - users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } } + users.uniq.to_a.compact.sort_by(&:username).map do |user| + { username: user.username, name: user.name } + end end def groups - @user.authorized_groups.sort_by(&:path).map { |group| { username: group.path, name: group.name } } + @user.authorized_groups.sort_by(&:path).map do |group| + count = group.users.count + { username: group.path, name: "#{group.name} (#{count})" } + end end def all_members - [{ username: "all", name: "Project and Group Members" }] + count = @project.team.members.flatten.count + [{ username: "all", name: "All Project and Group Members (#{count})" }] end end end diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb new file mode 100644 index 0000000000..a186c97628 --- /dev/null +++ b/app/services/projects/upload_service.rb @@ -0,0 +1,22 @@ +module Projects + class UploadService < BaseService + def initialize(project, file) + @project, @file = project, file + end + + def execute + return nil unless @file + + uploader = FileUploader.new(@project) + uploader.store!(@file) + + filename = uploader.image? ? uploader.file.basename : uploader.file.filename + + { + 'alt' => filename, + 'url' => uploader.secure_url, + 'is_image' => uploader.image? + } + end + end +end diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index b122b6c865..a9691bee46 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -3,8 +3,6 @@ class AttachmentUploader < CarrierWave::Uploader::Base storage :file - after :store, :reset_events_cache - def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end @@ -22,15 +20,7 @@ class AttachmentUploader < CarrierWave::Uploader::Base false end - def secure_url - Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" - end - def file_storage? self.class.storage == CarrierWave::Storage::File end - - def reset_events_cache(file) - model.reset_events_cache if model.is_a?(User) - end end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb new file mode 100644 index 0000000000..7cad044555 --- /dev/null +++ b/app/uploaders/avatar_uploader.rb @@ -0,0 +1,32 @@ +# encoding: utf-8 + +class AvatarUploader < CarrierWave::Uploader::Base + storage :file + + after :store, :reset_events_cache + + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + + def image? + img_ext = %w(png jpg jpeg gif bmp tiff) + if file.respond_to?(:extension) + img_ext.include?(file.extension.downcase) + else + # Not all CarrierWave storages respond to :extension + ext = file.path.split('.').last.downcase + img_ext.include?(ext) + end + rescue + false + end + + def file_storage? + self.class.storage == CarrierWave::Storage::File + end + + def reset_events_cache(file) + model.reset_events_cache if model.is_a?(User) + end +end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 0fa987c93f..f9673abbfe 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -2,40 +2,47 @@ class FileUploader < CarrierWave::Uploader::Base storage :file - def initialize(base_dir, path = '', allowed_extensions = nil) - @base_dir = base_dir - @path = path - @allowed_extensions = allowed_extensions + attr_accessor :project, :secret + + def initialize(project, secret = self.class.generate_secret) + @project = project + @secret = secret end def base_dir - @base_dir + "uploads" end def store_dir - File.join(@base_dir, @path) + File.join(base_dir, @project.path_with_namespace, @secret) end def cache_dir - File.join(@base_dir, 'tmp', @path) + File.join(base_dir, 'tmp', @project.path_with_namespace, @secret) end - def extension_white_list - @allowed_extensions + def self.generate_secret + SecureRandom.hex end - def store!(file) - @filename = self.class.generate_filename(file) - super + def secure_url + File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename) end - def self.generate_filename(file) - original_filename = File.basename(file.original_filename, '.*') - extension = File.extname(file.original_filename) - new_filename = Digest::MD5.hexdigest(original_filename) + extension + def file_storage? + self.class.storage == CarrierWave::Storage::File end - def self.generate_dir - SecureRandom.hex(5) + def image? + img_ext = %w(png jpg jpeg gif bmp tiff) + if file.respond_to?(:extension) + img_ext.include?(file.extension.downcase) + else + # Not all CarrierWave storages respond to :extension + ext = file.path.split('.').last.downcase + img_ext.include?(ext) + end + rescue + false end end diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 0f9cdfc9e8..3780500a44 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,5 +1,7 @@ .row - .col-md-3 + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %aside.col-md-3 .admin-filter = form_tag admin_namespaces_projects_path, method: :get, class: '' do .form-group @@ -36,7 +38,7 @@ = button_tag "Search", class: "btn submit btn-primary" = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" - .col-md-9 + %section.col-md-9 .panel.panel-default .panel-heading Projects (#{@projects.total_count}) diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 3bcf1cc9ed..1421c2ea90 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,6 +1,6 @@ %h3.page-title Project: #{@project.name_with_namespace} - = link_to edit_namespace_project_path(@project.namespace, @project), class: "btn pull-right" do + = link_to edit_project_path(@project), class: "btn pull-right" do %i.fa.fa-pencil-square-o Edit %hr @@ -13,7 +13,7 @@ %li %span.light Name: %strong - = link_to @project.name, namespace_project_path(@project.namespace, @project) + = link_to @project.name, project_path(@project) %li %span.light Namespace: %strong diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml index 1d3e192a32..0093fb9776 100644 --- a/app/views/admin/services/index.html.haml +++ b/app/views/admin/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/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 3f29cf720b..cd96bb4477 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,5 +1,7 @@ .row - .col-md-3 + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %aside.col-md-3 .admin-filter %ul.nav.nav-pills.nav-stacked %li{class: "#{'active' unless params[:filter]}"} @@ -27,7 +29,7 @@ %hr = link_to 'Reset', admin_users_path, class: "btn btn-cancel" - .col-md-9 + %section.col-md-9 .panel.panel-default .panel-heading Users (#{@users.total_count}) diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml index 3dd69df523..fa9179cb24 100644 --- a/app/views/dashboard/_project.html.haml +++ b/app/views/dashboard/_project.html.haml @@ -1,4 +1,4 @@ -= link_to namespace_project_path(project.namespace, project), class: dom_class(project) do += link_to project_path(project), class: dom_class(project) do .dash-project-avatar = project_icon(project, alt: '', class: 'avatar project-avatar s40') .dash-project-access-icon diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index e57e1e0939..03d4b3d8bb 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -16,10 +16,10 @@ %li.my-project-row %h4.project-title .pull-left - = project_icon("#{project.namespace.to_param}/#{project.to_param}", alt: '', class: 'avatar project-avatar s60') + = project_icon(project, alt: '', class: 'avatar project-avatar s60') .project-access-icon = visibility_level_icon(project.visibility_level) - = link_to namespace_project_path(project.namespace, project), class: dom_class(project) do + = link_to project_path(project), class: dom_class(project) do %strong= project.name_with_namespace - if project.forked_from_project diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index fa2460518f..89e4e229ac 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,6 +1,18 @@ %div - = render 'devise/shared/signin_box' + - if signin_enabled? || ldap_enabled? + = render 'devise/shared/signin_box' - - if signup_enabled? + -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box + - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? + .clearfix.prepend-top-20 + = render 'devise/shared/omniauth_box' + + -# Signup only makes sense if you can also sign-in + - if signin_enabled? && signup_enabled? .prepend-top-20 = render 'devise/shared/signup_box' + + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?) + %div + No authentication methods configured. diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml new file mode 100644 index 0000000000..4cd1c303b2 --- /dev/null +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -0,0 +1,10 @@ +%p + %span.light + Sign in with   + - providers = additional_providers + - providers.each do |provider| + %span.light + - if default_providers.include?(provider) + = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider) + - else + = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn" diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index 0acb853877..4ef18c0906 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -18,9 +18,9 @@ - note = event.target - if note.attachment.url - 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' - else - = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do + = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do %i.fa.fa-paperclip = note.attachment_identifier diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 2f28470f8b..b505760fa8 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -11,7 +11,7 @@ .nothing-here-block This group has no projects yet - projects.each do |project| %li.project-row - = link_to namespace_project_path(project.namespace, project), class: dom_class(project) do + = link_to project_path(project), class: dom_class(project) do .dash-project-avatar = project_icon(project, alt: '', class: 'avatar s40') .dash-project-access-icon diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index cd4c9fbf36..8d10722628 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -10,9 +10,16 @@ target_field.append("/" + project_name) target_field.data("project_name", project_name) target_field.find('input').prop("value", origin_namespace) +- elsif @access_denied + :plain + job = $("tr#repo_#{@repo_id}") + job.find(".import-actions").html("

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 "\'s GitLab" or "\'s GitLab" or something else descriptive. + - Application description: Fill this in if you wish. + - URL: The URL to your GitLab installation. 'https://gitlab.company.com' +1. Select "Save". + +1. You should now see a Key and Secret in the list of OAuth customers. + Keep this page open as you continue configuration. + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For instalations from source: + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. + +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "bitbucket", + "app_id" => "YOUR_KEY", + "app_secret" => "YOUR_APP_SECRET", + "url" => "https://bitbucket.org/" + } + ] + ``` + + For installation from source: + + ``` + - { name: 'bitbucket', app_id: 'YOUR_KEY', + app_secret: 'YOUR_APP_SECRET' } + ``` + +1. Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7. + +1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7. + +1. Save the configuration file. + +1. Restart GitLab for the changes to take effect. + +On the sign in page there should now be a Bitbucket icon below the regular sign in form. +Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application. +If everything goes well the user will be returned to GitLab and will be signed in. + +## Bitbucket project import + +To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com. + +Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key. + +### Step 1: Known hosts + +To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so: + +1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use: + + ```sh + ssh git@bitbucket.org + ``` + +1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter): + + ```sh + The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established. + RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40. + Are you sure you want to continue connecting (yes/no)? + ``` + +1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts. + +1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2: + +### Step 2: Public key + +To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/id_rsa.pub`, which will expand to `/home/git/.ssh/id_rsa.pub` in most configurations. + +If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: + +1. Create a new SSH key: + + ```sh + sudo -u git -H ssh-keygen + ``` + + Make sure to use an **empty passphrase**. + +2. Restart GitLab to allow it to find the new public key. + +You should now see the "Import projects from Bitbucket" option on the New Project page enabled. diff --git a/doc/integration/github.md b/doc/integration/github.md index 7eacf2c808..a5c2913151 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -1,6 +1,9 @@ -# GitHub OAuth2 OmniAuth Provider +# Integrate your server with GitHub -To enable the GitHub OmniAuth provider you must register your application with GitHub. GitHub will generate a client ID and secret key for you to use. +Import projects from GitHub and login to your GitLab instance with your GitHub account. + +To enable the GitHub OmniAuth provider you must register your application with GitHub. +GitHub will generate an application ID and secret key for you to use. 1. Sign in to GitHub. @@ -17,7 +20,9 @@ To enable the GitHub OmniAuth provider you must register your application with G - Authorization callback URL: 'https://gitlab.company.com/' 1. Select "Register application". -1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). Keep this page open as you continue configuration. ![GitHub app](github_app.png) +1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). + Keep this page open as you continue configuration. + ![GitHub app](github_app.png) 1. On your GitLab server, open the configuration file. @@ -35,7 +40,7 @@ To enable the GitHub OmniAuth provider you must register your application with G sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for inital settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. 1. Add the provider configuration: @@ -45,8 +50,8 @@ To enable the GitHub OmniAuth provider you must register your application with G gitlab_rails['omniauth_providers'] = [ { "name" => "github", - "app_id" => "YOUR APP ID", - "app_secret" => "YOUR APP SECRET", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", "url" => "https://github.com/", "args" => { "scope" => "user:email" } } } @@ -55,31 +60,34 @@ To enable the GitHub OmniAuth provider you must register your application with G For installation from source: + For GitHub.com: + ``` - - { name: 'github', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET', - url: "https://github.com/", - args: { scope: 'user:email' } } + - { name: 'github', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + args: { scope: 'user:email' } } ``` - -1. If you want to use GitHub Enterprise then your configuration should look like the following: + For GitHub Enterprise: ``` - - { name: 'github', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET', - url: "https://github.example.com/", - args: { scope: 'user:email' } } + - { name: 'github', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + url: "https://github.example.com/", + args: { scope: 'user:email' } } ``` + + __Replace `https://github.example.com/` with your GitHub URL__ +1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7. -1. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7. - -1. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7. +1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7. 1. Save the configuration file. 1. Restart GitLab for the changes to take effect. -On the sign in page there should now be a GitHub icon below the regular sign in form. Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in. +On the sign in page there should now be a GitHub icon below the regular sign in form. +Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. +If everything goes well the user will be returned to GitLab and will be signed in. diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md index 87400bed5b..49ffaa62af 100644 --- a/doc/integration/gitlab.md +++ b/doc/integration/gitlab.md @@ -3,7 +3,7 @@ Import projects from GitLab.com and login to your GitLab instance with your GitLab.com account. To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com. -GitLab.com will generate a application ID and secret key for you to use. +GitLab.com will generate an application ID and secret key for you to use. 1. Sign in to GitLab.com @@ -46,7 +46,7 @@ GitLab.com will generate a application ID and secret key for you to use. sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for inital settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. 1. Add the provider configuration: @@ -56,8 +56,8 @@ GitLab.com will generate a application ID and secret key for you to use. gitlab_rails['omniauth_providers'] = [ { "name" => "gitlab", - "app_id" => "YOUR APP ID", - "app_secret" => "YOUR APP SECRET", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", "args" => { "scope" => "api" } } } ] @@ -66,14 +66,14 @@ GitLab.com will generate a application ID and secret key for you to use. For installations from source: ``` - - { 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' } } ``` -1. Change 'YOUR APP ID' to the Application ID from the GitLab application page. +1. Change 'YOUR_APP_ID' to the Application ID from the GitLab.com application page. -1. Change 'YOUR APP SECRET' to the secret from the GitLab application page. +1. Change 'YOUR_APP_SECRET' to the secret from the GitLab.com application page. 1. Save the configuration file. @@ -81,4 +81,4 @@ GitLab.com will generate a application ID and secret key for you to use. On the sign in page there should now be a GitLab.com icon below the regular sign in form. Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application. -If everything goes well the user will be returned to your GitLab instance and will be signed in. +If everything goes well the user will be returned to your GitLab instance and will be signed in. \ No newline at end of file diff --git a/doc/integration/google.md b/doc/integration/google.md index 168077c277..d7b741ece6 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -43,7 +43,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for inital settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. 1. Add the provider configuration: @@ -53,8 +53,8 @@ To enable the Google OAuth2 OmniAuth provider you must register your application gitlab_rails['omniauth_providers'] = [ { "name" => "google_oauth2", - "app_id" => "YOUR APP ID", - "app_secret" => "YOUR APP SECRET", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", "args" => { "access_type" => "offline", "approval_prompt" => '' } } } ] @@ -63,14 +63,14 @@ To enable the Google OAuth2 OmniAuth provider you must register your application For installations from source: ``` - - { 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: '' } } ``` -1. Change 'YOUR APP ID' to the client ID from the Google Developer page from step 10. +1. Change 'YOUR_APP_ID' to the client ID from the Google Developer page from step 10. -1. Change 'YOUR APP SECRET' to the client secret from the Google Developer page from step 10. +1. Change 'YOUR_APP_SECRET' to the client secret from the Google Developer page from step 10. 1. Save the configuration file. diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index c92fa3ee4b..24f7b4bb4b 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -70,6 +70,7 @@ Now we can choose one or more of the Supported Providers below to continue confi ## Supported Providers - [GitHub](github.md) +- [Bitbucket](bitbucket.md) - [GitLab.com](gitlab.md) - [Google](google.md) - [Shibboleth](shibboleth.md) diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md index 2d517b2fbc..fe9091ad9a 100644 --- a/doc/integration/twitter.md +++ b/doc/integration/twitter.md @@ -47,7 +47,7 @@ To enable the Twitter OmniAuth provider you must register your application with sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for inital settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. 1. Add the provider configuration: @@ -57,8 +57,8 @@ To enable the Twitter OmniAuth provider you must register your application with gitlab_rails['omniauth_providers'] = [ { "name" => "twitter", - "app_id" => "YOUR APP ID", - "app_secret" => "YOUR APP SECRET" + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET" } ] ``` @@ -66,13 +66,13 @@ To enable the Twitter OmniAuth provider you must register your application with For installations from source: ``` - - { name: 'twitter', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET' } + - { name: 'twitter', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET' } ``` -1. Change 'YOUR APP ID' to the API key from Twitter page in step 11. +1. Change 'YOUR_APP_ID' to the API key from Twitter page in step 11. -1. Change 'YOUR APP SECRET' to the API secret from the Twitter page in step 11. +1. Change 'YOUR_APP_SECRET' to the API secret from the Twitter page in step 11. 1. Save the configuration file. diff --git a/doc/logs/logs.md b/doc/logs/logs.md index 07302894dd..ec0109a426 100644 --- a/doc/logs/logs.md +++ b/doc/logs/logs.md @@ -1,6 +1,8 @@ ## Log system GitLab has advanced log system so everything is logging and you can analize your instance using various system log files. -These log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files. +In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://doc.gitlab.com/ee/administration/audit_events.html) + +System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files. #### production.log This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/logs/production.log` for installations from the source. @@ -12,7 +14,7 @@ This task is more useful for GitLab contributors and developers. Use part of thi Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200 Processing by Projects::TreeController#show as HTML Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"} - + ... [CUT OUT] amespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1 [["id", 26]] diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md new file mode 100644 index 0000000000..780a45bca2 --- /dev/null +++ b/doc/project_services/irker.md @@ -0,0 +1,46 @@ +# Irker IRC Gateway + +GitLab provides a way to push update messages to an Irker server. When +configured, pushes to a project will trigger the service to send data directly +to the Irker server. + +See the project homepage for further info: http://www.catb.org/esr/irker/ + +## Needed setup + +You will first need an Irker daemon. You can download the Irker code from its +gitorious repository on https://gitorious.org/irker: `git clone +git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can +run the python script named `irkerd`. This script is the gateway script, it acts +both as an IRC client, for sending messages to an IRC server obviously, and as a +TCP server, for receiving messages from the GitLab service. + +If the Irker server runs on the same machine, you are done. If not, you will +need to follow the firsts steps of the next section. + +## Optional setup + +In the `app/models/project_services/irker_service.rb` file, you can modify some +options in the `initialize_settings` method: +- **server_ip** (defaults to `localhost`): the server IP address where the +`irkerd` daemon runs; +- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon; +- **max_channels** (defaults to `3`): the maximum number of recipients the +client is authorized to join, per project; +- **default_irc_uri** (no default) : if this option is set, it has to be in the +format `irc[s]://domain.name` and will be prepend to each and every channel +provided by the user which is not a full URI. + +If the Irker server and the GitLab application do not run on the same host, you +will **need** to setup at least the **server_ip** option. + +## Note on Irker recipients + +Irker accepts channel names of the form `chan` and `#chan`, both for the +`#chan` channel. If you want to send messages in query, you will need to add +`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter +case, `Aorimn` is treated as a nick and no more as a channel name. + +Irker can also join password-protected channels. Users need to append +`?key=thesecretpassword` to the chan name. + diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md index 93a57485cf..86eda341d6 100644 --- a/doc/project_services/project_services.md +++ b/doc/project_services/project_services.md @@ -13,6 +13,7 @@ __Project integrations with external services for continuous integration and mor - Gemnasium - GitLab CI - HipChat +- [Irker](irker.md) An IRC gateway to receive messages on repository updates. - Pivotal Tracker - Pushover - Slack diff --git a/doc/release/howto_rc1.md b/doc/release/howto_rc1.md index c4156d25d5..07c703142d 100644 --- a/doc/release/howto_rc1.md +++ b/doc/release/howto_rc1.md @@ -27,7 +27,7 @@ Make sure the code quality indicators are green / good. - [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) -### 4. Run release tool for CE and EE +### 4. Run release tool **Make sure EE `master` has latest changes from CE `master`** @@ -38,8 +38,8 @@ git clone git@dev.gitlab.org:gitlab/release-tools.git cd release-tools ``` -Release candidate creates stable branch from master. -So we need to sync master branch between all CE remotes. Also do same for EE. +Release candidate creates stable branch from master. +So we need to sync master branch between all CE, EE and CI remotes. ``` bundle exec rake sync @@ -53,22 +53,3 @@ bundle exec rake release["x.x.0.rc1"] Now developers can use master for merging new features. So you should use stable branch for future code changes related to release. - - -### 5. Release GitLab CI RC1 - -Add to your local `gitlab-ci/.git/config`: - -``` -[remote "public"] - url = none - pushurl = git@dev.gitlab.org:gitlab/gitlab-ci.git - pushurl = git@gitlab.com:gitlab-org/gitlab-ci.git - pushurl = git@github.com:gitlabhq/gitlab-ci.git -``` - -* Create a stable branch `x-y-stable` -* Bump VERSION to `x.y.0.rc1` -* `git tag -a v$(cat VERSION) -m "Version $(cat VERSION)"` -* `git push public x-y-stable v$(cat VERSION)` - diff --git a/doc/release/monthly.md b/doc/release/monthly.md index c9e6d3426b..dd44c1eb86 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -15,7 +15,7 @@ This person should also make sure this document is kept up to date and issues ar ## Take vacations into account -The time is measured in weekdays to compensate for weekends. +The time is measured in weekdays to compensate for weekends. Do everything on time to prevent problems due to rush jobs or too little testing time. Make sure that you take into account any vacations of maintainers. If the release is falling behind immediately warn the team. @@ -38,29 +38,30 @@ Xth: (7 working days before the 22nd) Xth: (6 working days before the 22nd) - [ ] Merge CE master in to EE master via merge request (#LINK) -- [ ] Create CE, EE, CI RC1 versions (#LINK) - [ ] Determine QA person and notify this person +- [ ] Check the tasks in [how to rc1 guide](howto_rc1.md) and delegate tasks if necessary +- [ ] Create CE, EE, CI RC1 versions (#LINK) Xth: (5 working days before the 22nd) - [ ] Do QA and fix anything coming out of it (#LINK) - [ ] Close the omnibus-gitlab milestone +- [ ] Prepare the blog post (#LINK) Xth: (4 working days before the 22nd) -- [ ] Build rc1 package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) - [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) +- [ ] Create regression issues (CE, CI) (#LINK) +- [ ] Tweet about rc1 (#LINK) Xth: (3 working days before the 22nd) -- [ ] Create regression issues (CE, CI) (#LINK) -- [ ] Tweet about rc1 (#LINK) -- [ ] Prepare the blog post (#LINK) +- [ ] Merge CE stable branch into EE stable branch Xth: (2 working days before the 22nd) -- [ ] Merge CE stable branch into EE stable branch - [ ] Check that everyone is mentioned on the blog post (the reviewer should have done this one working day ago) +- [ ] Check that MVP is added to the mvp page (source/mvp/index.html in www-gitlab-com) Xth: (1 working day before the 22nd) @@ -93,13 +94,13 @@ There are three changelogs that need to be updated: CE, EE and CI. ## Prepare CHANGELOG for next release -Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version. +Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version, usually X.X.X.pre. ## QA Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X QA" in order to keep track of the progress. -Use the omnibus packages of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md). +Use the omnibus packages created for RC1 of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md). **NOTE** Upgrader can only be tested when tags are pushed to all repositories. Do not forget to confirm it is working before releasing. Note that in the issue. @@ -112,8 +113,7 @@ create an issue about it in order to discuss the next steps after the release. ## Update GitLab.com with RC1 -Merge the RC1 EE code into GitLab.com. -Once the build is green, create a package. +Use the omnibus EE packages created for RC1. If there are big database migrations consider testing them with the production db on a VM. Try to deploy in the morning. It is important to do this as soon as possible, so we can catch any errors before we release the full version. @@ -127,7 +127,7 @@ Please do not raise issues directly in this issue but link to issues that might The decision to create a patch release or not is with the release manager who is assigned to this issue. The release manager will comment here about the plans for patch releases. -Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately. +Assign the issue to the release manager and at mention all members of gitlab core team. If there are any known bugs in the release add them immediately. ## Tweet about RC1 @@ -143,8 +143,8 @@ Tweet about the RC release: 1. Also check the CI changelog 1. Add a proposed tweet text to the blog post WIP MR description. 1. Create a WIP MR for the blog post -1. Ask Dmitriy to add screenshots to the WIP MR. -1. Decide with team who will be the MVP user. +1. Ask Dmitriy (or a team member with OS X) to add screenshots to the WIP MR. +1. Decide with core team who will be the MVP user. 1. Create WIP MR for adding MVP to MVP page on website 1. Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. 1. Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master) @@ -166,11 +166,7 @@ Bump version, create release tag and push to remotes: bundle exec rake release["x.x.0"] ``` -Also perform these steps for GitLab CI: - -1. bump version in the stable branch -1. create annotated tag -1. push the stable branch and the annotated tag to the public repositories +This will create correct version and tag and push to all CE, EE and CI remotes. Update [installation.md](/doc/install/installation.md) to the newest version in master. diff --git a/doc/release/patch.md b/doc/release/patch.md index d8bb4aef0e..80afa19b6c 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -51,6 +51,6 @@ CE=false be rake release['x.x.x'] 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) 1. Apply the patch to GitLab.com and the private GitLab development server -1. Create and publish a blog post +1. Create and publish a blog post, see [patch release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/patch_release_blog_template.md) 1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) diff --git a/doc/update/6.x-or-7.x-to-7.8.md b/doc/update/6.x-or-7.x-to-7.8.md index 497546c06a..61ab10b0bf 100644 --- a/doc/update/6.x-or-7.x-to-7.8.md +++ b/doc/update/6.x-or-7.x-to-7.8.md @@ -181,7 +181,7 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab ### Change Nginx settings * HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stablef/lib/support/nginx/gitlab-ssl but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-8-stable/lib/support/nginx/gitlab-ssl but with your settings. * A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section. ## 9. Start application diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 6af940cca3..50941db25f 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -12,7 +12,7 @@ sudo service gitlab stop # Update /home/git/gitlab/config/database.yml -git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd mysql-postgresql-converter mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production -p python db_converter.py databasename.mysql databasename.psql @@ -39,7 +39,7 @@ On non-omnibus installations (distributed using Git) we retrieve the index decla ``` # Clone the database converter on your Postgres-backed GitLab server cd /tmp -git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd /home/git/gitlab @@ -60,7 +60,7 @@ On omnibus-gitlab we need to get the index declarations from a file called `sche ``` # Clone the database converter on your Postgres-backed GitLab server cd /tmp -/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd /tmp/mysql-postgresql-converter # Download schema.rb.bundled if necessary @@ -76,7 +76,7 @@ test -e /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb.bundled || sudo / ## Converting a GitLab backup file from MySQL to Postgres **Note:** Please make sure to have Python 2.7.x (or higher) installed. -GitLab backup files (_gitlab_backup.tar) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server. +GitLab backup files (`_gitlab_backup.tar`) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server. ``` # Stop GitLab @@ -98,7 +98,7 @@ cd tmp/backups/postgresql sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p # Clone the database converter -sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab # Convert gitlabhq_production.mysql sudo -u git -H mkdir db diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md index bcadf5e8c0..7fc8f96b9e 100644 --- a/doc/workflow/web_editor.md +++ b/doc/workflow/web_editor.md @@ -10,7 +10,7 @@ to create the first file and open it in the web editor. ![web editor 1](web_editor/empty_project.png) -Fill in a file name, some content, a commit message and press the commit button. +Fill in a file name, some content, a commit message, branch name and press the commit button. The file will be saved to the repository. ![web editor 2](web_editor/new_file.png) @@ -21,6 +21,6 @@ viewing the file. ![web editor 3](web_editor/show_file.png) Editing a file is almost the same as creating a new file, -with as addition the ability to preview your changes in a separate tab. +with as addition the ability to preview your changes in a separate tab. Also you can save your change to another branch by filling out field `branch` ![web editor 3](web_editor/edit_file.png) diff --git a/doc/workflow/web_editor/edit_file.png b/doc/workflow/web_editor/edit_file.png index 1522c50b62..f480c69ac3 100644 Binary files a/doc/workflow/web_editor/edit_file.png and b/doc/workflow/web_editor/edit_file.png differ diff --git a/doc/workflow/web_editor/new_file.png b/doc/workflow/web_editor/new_file.png index 80941f37ce..55ebd9e025 100644 Binary files a/doc/workflow/web_editor/new_file.png and b/doc/workflow/web_editor/new_file.png differ diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000000..dd449725e1 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1 @@ +*.md diff --git a/docker/Dockerfile b/docker/Dockerfile index cfb89357a6..3584a754c6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update -q \ # If the Omnibus package version below is outdated please contribute a merge request to update it. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it. RUN TMP_FILE=$(mktemp); \ - wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.0-omnibus-1_amd64.deb \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.1-omnibus-1_amd64.deb \ && dpkg -i $TMP_FILE \ && rm -f $TMP_FILE @@ -26,9 +26,12 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \ # Expose web & ssh EXPOSE 80 22 -# Volume & configuration +# Declare volumes VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"] -ADD gitlab.rb /etc/gitlab/ -# Default is to run runit & reconfigure -CMD gitlab-ctl reconfigure & /opt/gitlab/embedded/bin/runsvdir-start \ No newline at end of file +# Copy assets +COPY assets/gitlab.rb /etc/gitlab/ +COPY assets/wrapper /usr/local/bin/ + +# Wrapper to handle signal, trigger runit and reconfigure GitLab +CMD ["/usr/local/bin/wrapper"] diff --git a/docker/gitlab.rb b/docker/assets/gitlab.rb similarity index 100% rename from docker/gitlab.rb rename to docker/assets/gitlab.rb diff --git a/docker/assets/wrapper b/docker/assets/wrapper new file mode 100755 index 0000000000..9e6e7a0590 --- /dev/null +++ b/docker/assets/wrapper @@ -0,0 +1,17 @@ +#!/bin/bash + +function sigterm_handler() { + echo "SIGTERM signal received, try to gracefully shutdown all services..." + gitlab-ctl stop +} + +trap "sigterm_handler; exit" TERM + +function entrypoint() { + # Default is to run runit and reconfigure GitLab + gitlab-ctl reconfigure & + /opt/gitlab/embedded/bin/runsvdir-start & + wait +} + +entrypoint diff --git a/features/project/service.feature b/features/project/service.feature index 1482faa83a..365221dbe1 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -67,6 +67,12 @@ Feature: Project Services And I fill jira settings Then I should see jira service settings saved + Scenario: Activate Irker (IRC Gateway) service + When I visit project "Shop" services page + And I click Irker service link + And I fill Irker settings + Then I should see Irker service settings saved + Scenario: Activate Atlassian Bamboo CI service When I visit project "Shop" services page And I click Atlassian Bamboo CI service link diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index ee8d0bffa9..90b966dd64 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -34,6 +34,17 @@ Feature: Project Source Browse Files Then I am redirected to the new file And I should see its new content + @javascript + Scenario: I can create and commit file and specify new branch + Given I click on "new file" link in repo + And I edit code + And I fill the new file name + And I fill the commit message + And I fill the new branch name + And I click on "Commit Changes" + Then I am redirected to the new file on new branch + And I should see its new content + @javascript @tricky Scenario: I can create file in empty repo Given I own an empty project @@ -83,6 +94,17 @@ Feature: Project Source Browse Files Then I am redirected to the ".gitignore" And I should see its new content + @javascript + Scenario: I can edit and commit file to new branch + Given I click on ".gitignore" file in repo + And I click button "Edit" + And I edit code + And I fill the commit message + And I fill the new branch name + And I click on "Commit Changes" + Then I am redirected to the ".gitignore" on new branch + And I should see its new content + @javascript @wip Scenario: If I don't change the content of the file I see an error message Given I click on ".gitignore" file in repo diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 0955187d1c..012ec28db4 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -148,7 +148,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I should see new group "Owned" avatar' do - Group.find_by(name: "Owned").avatar.should be_instance_of AttachmentUploader + Group.find_by(name: "Owned").avatar.should be_instance_of AvatarUploader Group.find_by(name: "Owned").avatar.url.should == "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png" end diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb index df96dddd06..13e93618eb 100644 --- a/features/steps/profile/notifications.rb +++ b/features/steps/profile/notifications.rb @@ -7,6 +7,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps end step 'I should see global notifications settings' do - page.should have_content "Notifications settings" + page.should have_content "Notifications Settings" end end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index a907b0b7dc..bfbfe7af19 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -3,7 +3,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps include SharedPaths step 'I should see my profile info' do - page.should have_content "Profile settings" + page.should have_content "Profile Settings" end step 'I change my profile info' do @@ -29,7 +29,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step 'I should see new avatar' do - @user.avatar.should be_instance_of AttachmentUploader + @user.avatar.should be_instance_of AvatarUploader @user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png" end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 0c1db8e312..7fff00ad2f 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -213,7 +213,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see a comment like "Line is wrong" in the second file' do - within '.files [id^=diff]:nth-child(2) .note-text' do + within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do page.should have_visible_content "Line is wrong" end end @@ -225,7 +225,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see a comment like "Line is wrong here" in the second file' do - within '.files [id^=diff]:nth-child(2) .note-text' do + within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do page.should have_visible_content "Line is wrong here" end end @@ -238,7 +238,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps click_button "Add Comment" end - within ".files [id^=diff]:nth-child(1) .note-text" do + within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do page.should have_content "Line is correct" end end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 09c215eff8..ed5449317c 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -38,7 +38,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I should see new project avatar' do - @project.avatar.should be_instance_of AttachmentUploader + @project.avatar.should be_instance_of AvatarUploader url = @project.avatar.url url.should == "/uploads/project/avatar/#{ @project.id }/gitlab_logo.png" end diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 25c24eb76a..10499ba3cd 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -17,6 +17,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps page.should have_content 'Atlassian Bamboo' page.should have_content 'JetBrains TeamCity' page.should have_content 'Asana' + page.should have_content 'Irker (IRC gateway)' end step 'I click gitlab-ci service link' do @@ -132,6 +133,22 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps find_field('Recipients').value.should == 'qa@company.name' end + step 'I click Irker service link' do + click_link 'Irker (IRC gateway)' + end + + step 'I fill Irker settings' do + check 'Active' + fill_in 'Recipients', with: 'irc://chat.freenode.net/#commits' + check 'Colorize messages' + click_button 'Save' + end + + step 'I should see Irker service settings saved' do + find_field('Recipients').value.should == 'irc://chat.freenode.net/#commits' + find_field('Colorize messages').value.should == '1' + end + step 'I click Slack service link' do click_link 'Slack' end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 98d8a60e1a..557555aee5 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -69,6 +69,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps fill_in :file_name, with: new_file_name end + step 'I fill the new branch name' do + fill_in :new_branch, with: 'new_branch_name' + end + step 'I fill the new file name with an illegal name' do fill_in :file_name, with: '.git' end @@ -148,6 +152,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore')) end + step 'I am redirected to the ".gitignore" on new branch' do + expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/.gitignore')) + end + step 'I am redirected to the permalink URL' do expect(current_path).to( eq(namespace_project_blob_path(@project.namespace, @project, @@ -161,6 +169,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps @project.namespace, @project, 'master/' + new_file_name)) end + step 'I am redirected to the new file on new branch' do + expect(current_path).to eq(namespace_project_blob_path( + @project.namespace, @project, 'new_branch_name/' + new_file_name)) + end + step "I don't see the permalink link" do expect(page).not_to have_link('permalink') end @@ -177,7 +190,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps click_link 'add a file' # Remove pre-receive hook so we can push without auth - FileUtils.rm(File.join(@project.repository.path, 'hooks', 'pre-receive')) + FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive')) end private diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 18d7af6a32..d2a5edef1a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -61,6 +61,7 @@ module API expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at expose :namespace expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } + expose :avatar_url end class ProjectMember < UserBasic diff --git a/lib/api/users.rb b/lib/api/users.rb index 81865962f0..efe45f7f12 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -57,15 +57,18 @@ module API # bio - Bio # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false + # confirm - Require user confirmation - true (default) or false # Example Request: # POST /users post do authenticated_as_admin! required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin, :confirm] user = User.build_user(attrs) admin = attrs.delete(:admin) user.admin = admin unless admin.nil? + confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i)) + user.skip_confirmation! unless confirm identity_attrs = attributes_for_keys [:provider, :extern_uid] if identity_attrs.any? diff --git a/lib/gitlab/bitbucket_import.rb b/lib/gitlab/bitbucket_import.rb new file mode 100644 index 0000000000..7298152e7e --- /dev/null +++ b/lib/gitlab/bitbucket_import.rb @@ -0,0 +1,6 @@ +module Gitlab + module BitbucketImport + mattr_accessor :public_key + @public_key = nil + end +end diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb new file mode 100644 index 0000000000..c907bebaef --- /dev/null +++ b/lib/gitlab/bitbucket_import/client.rb @@ -0,0 +1,99 @@ +module Gitlab + module BitbucketImport + class Client + attr_reader :consumer, :api + + def initialize(access_token = nil, access_token_secret = nil) + @consumer = ::OAuth::Consumer.new( + config.app_id, + config.app_secret, + bitbucket_options + ) + + if access_token && access_token_secret + @api = ::OAuth::AccessToken.new(@consumer, access_token, access_token_secret) + end + end + + def request_token(redirect_uri) + request_token = consumer.get_request_token(oauth_callback: redirect_uri) + + { + oauth_token: request_token.token, + oauth_token_secret: request_token.secret, + oauth_callback_confirmed: request_token.callback_confirmed?.to_s + } + end + + def authorize_url(request_token, redirect_uri) + request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash) + + if request_token.callback_confirmed? + request_token.authorize_url + else + request_token.authorize_url(oauth_callback: redirect_uri) + end + end + + def get_token(request_token, oauth_verifier, redirect_uri) + request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash) + + if request_token.callback_confirmed? + request_token.get_access_token(oauth_verifier: oauth_verifier) + else + request_token.get_access_token(oauth_callback: redirect_uri) + end + end + + def user + JSON.parse(api.get("/api/1.0/user").body) + end + + def issues(project_identifier) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues").body) + end + + def issue_comments(project_identifier, issue_id) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body) + end + + def project(project_identifier) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}").body) + end + + def find_deploy_key(project_identifier, key) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find do |deploy_key| + deploy_key["key"].chomp == key.chomp + end + end + + def add_deploy_key(project_identifier, key) + deploy_key = find_deploy_key(project_identifier, key) + return if deploy_key + + JSON.parse(api.post("/api/1.0/repositories/#{project_identifier}/deploy-keys", key: key, label: "GitLab import key").body) + end + + def delete_deploy_key(project_identifier, key) + deploy_key = find_deploy_key(project_identifier, key) + return unless deploy_key + + api.delete("/api/1.0/repositories/#{project_identifier}/deploy-keys/#{deploy_key["pk"]}").code == "204" + end + + def projects + JSON.parse(api.get("/api/1.0/user/repositories").body).select { |repo| repo["scm"] == "git" } + end + + private + + def config + Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"} + end + + def bitbucket_options + OmniAuth::Strategies::Bitbucket.default_options[:client_options] + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb new file mode 100644 index 0000000000..42c93707ca --- /dev/null +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -0,0 +1,52 @@ +module Gitlab + module BitbucketImport + class Importer + attr_reader :project, :client + + def initialize(project) + @project = project + @client = Client.new(project.creator.bitbucket_access_token, project.creator.bitbucket_access_token_secret) + @formatter = Gitlab::ImportFormatter.new + end + + def execute + project_identifier = project.import_source + + return true unless client.project(project_identifier)["has_issues"] + + #Issues && Comments + issues = client.issues(project_identifier) + + issues["issues"].each do |issue| + body = @formatter.author_line(issue["reported_by"]["username"], issue["content"]) + + comments = client.issue_comments(project_identifier, issue["local_id"]) + + if comments.any? + body += @formatter.comments_header + end + + comments.each do |comment| + body += @formatter.comment(comment["author_info"]["username"], comment["utc_created_on"], comment["content"]) + end + + project.issues.create!( + description: body, + title: issue["title"], + state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened', + author_id: gl_user_id(project, issue["reported_by"]["username"]) + ) + end + + true + end + + private + + def gl_user_id(project, bitbucket_id) + user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s) + (user && user.id) || project.creator_id + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/key_adder.rb b/lib/gitlab/bitbucket_import/key_adder.rb new file mode 100644 index 0000000000..9931aa7e02 --- /dev/null +++ b/lib/gitlab/bitbucket_import/key_adder.rb @@ -0,0 +1,23 @@ +module Gitlab + module BitbucketImport + class KeyAdder + attr_reader :repo, :current_user, :client + + def initialize(repo, current_user) + @repo, @current_user = repo, current_user + @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + end + + def execute + return false unless BitbucketImport.public_key.present? + + project_identifier = "#{repo["owner"]}/#{repo["slug"]}" + client.add_deploy_key(project_identifier, BitbucketImport.public_key) + + true + rescue + false + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb new file mode 100644 index 0000000000..1a24a86fc3 --- /dev/null +++ b/lib/gitlab/bitbucket_import/key_deleter.rb @@ -0,0 +1,23 @@ +module Gitlab + module BitbucketImport + class KeyDeleter + attr_reader :project, :current_user, :client + + def initialize(project) + @project = project + @current_user = project.creator + @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + end + + def execute + return false unless BitbucketImport.public_key.present? + + client.delete_deploy_key(project.import_source, BitbucketImport.public_key) + + true + rescue + false + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb new file mode 100644 index 0000000000..db33af2c2d --- /dev/null +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -0,0 +1,39 @@ +module Gitlab + module BitbucketImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + @project = Project.new( + name: repo["name"], + path: repo["slug"], + description: repo["description"], + namespace: namespace, + creator: current_user, + visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, + import_type: "bitbucket", + import_source: "#{repo["owner"]}/#{repo["slug"]}", + import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git" + ) + + if @project.save! + @project.reload + + if @project.import_failed? + @project.import_retry + else + @project.import_start + end + end + + @project + end + end + end +end diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index aab260fcfb..7e6e1e61ae 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -46,7 +46,7 @@ module Gitlab end def github_options - config["args"]["client_options"].deep_symbolize_keys + OmniAuth::Strategies::GitHub.default_options[:client_options] end end end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index bc2b645b2d..23832b3233 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -20,7 +20,7 @@ module Gitlab body += @formatter.comments_header client.issue_comments(project.import_source, issue.number).each do |c| - body += @formatter.comment_to_md(c.user.login, c.created_at, c.body) + body += @formatter.comment(c.user.login, c.created_at, c.body) end end diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb index 2206b68da9..ecf4ff94e3 100644 --- a/lib/gitlab/gitlab_import/client.rb +++ b/lib/gitlab/gitlab_import/client.rb @@ -9,7 +9,7 @@ module Gitlab @client = ::OAuth2::Client.new( config.app_id, config.app_secret, - github_options + gitlab_options ) if access_token @@ -70,12 +70,8 @@ module Gitlab Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"} end - def github_options - { - site: 'https://gitlab.com/', - authorize_url: 'oauth/authorize', - token_url: 'oauth/token' - } + def gitlab_options + OmniAuth::Strategies::GitLab.default_options[:client_options] end end end diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index 5f9b14399a..c5304a0699 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -25,7 +25,7 @@ module Gitlab end comments.each do |comment| - body += @formatter.comment_to_md(comment["author"]["name"], comment["created_at"], comment["body"]) + body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"]) end project.issues.create!( diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb index ebb4b87f7e..72e041a90b 100644 --- a/lib/gitlab/import_formatter.rb +++ b/lib/gitlab/import_formatter.rb @@ -1,6 +1,6 @@ module Gitlab class ImportFormatter - def comment_to_md(author, date, body) + def comment(author, date, body) "\n\n*By #{author} on #{date}*\n\n#{body}" end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index a1fd794aed..d85c2ee4f2 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -121,7 +121,7 @@ module Gitlab text end - NAME_STR = '[a-zA-Z][a-zA-Z0-9_\-\.]*' + NAME_STR = '[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*' PROJ_STR = "(?#{NAME_STR}/#{NAME_STR})" REFERENCE_PATTERN = %r{ diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb index 82d71ab990..3cb9c0b5ec 100644 --- a/lib/gitlab/satellite/files/edit_file_action.rb +++ b/lib/gitlab/satellite/files/edit_file_action.rb @@ -10,7 +10,7 @@ module Gitlab # Returns false if committing the change fails # Returns false if pushing from the satellite to bare repo failed or was rejected # Returns true otherwise - def commit!(content, commit_message, encoding) + def commit!(content, commit_message, encoding, new_branch = nil) in_locked_and_timed_satellite do |repo| prepare_satellite!(repo) @@ -42,10 +42,12 @@ module Gitlab end + target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref + # push commit back to bare repo # will raise CommandFailed when push fails begin - repo.git.push({ raise: true, timeout: true }, :origin, ref) + repo.git.push({ raise: true, timeout: true }, :origin, target_branch) rescue Grit::Git::CommandFailed => ex log_and_raise(PushFailed, ex.message) end diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb index 69f7ffa94e..724dfa0d04 100644 --- a/lib/gitlab/satellite/files/new_file_action.rb +++ b/lib/gitlab/satellite/files/new_file_action.rb @@ -9,7 +9,7 @@ module Gitlab # Returns false if committing the change fails # Returns false if pushing from the satellite to bare repo failed or was rejected # Returns true otherwise - def commit!(content, commit_message, encoding) + def commit!(content, commit_message, encoding, new_branch = nil) in_locked_and_timed_satellite do |repo| prepare_satellite!(repo) @@ -45,9 +45,15 @@ module Gitlab # will raise CommandFailed when commit fails repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + target_branch = if new_branch.present? && !@project.empty_repo? + "#{ref}:#{new_branch}" + else + "#{current_ref}:#{ref}" + end + # push commit back to bare repo # will raise CommandFailed when push fails - repo.git.push({ raise: true, timeout: true }, :origin, "#{current_ref}:#{ref}") + repo.git.push({ raise: true, timeout: true }, :origin, target_branch) # everything worked true diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb new file mode 100644 index 0000000000..5dd4124061 --- /dev/null +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +describe Import::BitbucketController do + let(:user) { create(:user, bitbucket_access_token: 'asd123', bitbucket_access_token_secret: "sekret") } + + before do + sign_in(user) + controller.stub(:bitbucket_import_enabled?).and_return(true) + end + + describe "GET callback" do + before do + session[:oauth_request_token] = {} + end + + it "updates access token" do + token = "asdasd12345" + secret = "sekrettt" + access_token = double(token: token, secret: secret) + Gitlab::BitbucketImport::Client.any_instance.stub(:get_token).and_return(access_token) + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket") + + get :callback + + expect(user.reload.bitbucket_access_token).to eq(token) + expect(user.reload.bitbucket_access_token_secret).to eq(secret) + expect(controller).to redirect_to(status_import_bitbucket_url) + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(slug: 'vim', owner: 'asd') + end + + it "assigns variables" do + @project = create(:project, import_type: 'bitbucket', creator_id: user.id) + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end + + describe "POST create" do + before do + @repo = { + slug: 'vim', + owner: "john" + }.with_indifferent_access + end + + it "takes already existing namespace" do + namespace = create(:namespace, name: "john", owner: user) + expect(Gitlab::BitbucketImport::KeyAdder). + to receive(:new).with(@repo, user). + and_return(double(execute: true)) + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(@repo, namespace, user). + and_return(double(execute: true)) + controller.stub_chain(:client, :project).and_return(@repo) + + post :create, format: :js + end + end +end diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index 4cc4fb271e..b3affdb2ce 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -5,6 +5,7 @@ describe Import::GithubController do before do sign_in(user) + controller.stub(:github_import_enabled?).and_return(true) end describe "GET callback" do diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 287aa315db..b6b86b1bce 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -5,6 +5,7 @@ describe Import::GitlabController do before do sign_in(user) + controller.stub(:gitlab_import_enabled?).and_return(true) end describe "GET callback" do diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb new file mode 100644 index 0000000000..596d8d34b7 --- /dev/null +++ b/spec/controllers/projects/protected_branches_controller_spec.rb @@ -0,0 +1,10 @@ +require('spec_helper') + +describe Projects::ProtectedBranchesController do + describe "GET #index" do + let(:project) { create(:project_empty_repo, :public) } + it "redirect empty repo to projects page" do + get(:index, namespace_id: project.namespace.to_param, project_id: project.to_param) + end + end +end diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb new file mode 100644 index 0000000000..029f48b2d7 --- /dev/null +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -0,0 +1,57 @@ +require('spec_helper') + +describe Projects::UploadsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } + + describe "POST #create" do + before do + sign_in(user) + project.team << [user, :developer] + end + + context "without params['file']" do + it "returns an error" do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + format: :json + expect(response.status).to eq(422) + end + end + + context 'with valid image' do + before do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + file: jpg, + format: :json + end + + it 'returns a content with original filename, new link, and correct type.' do + expect(response.body).to match '\"alt\":\"rails_sample\"' + expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads" + expect(response.body).to match '\"is_image\":true' + end + end + + context 'with valid non-image file' do + before do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + file: txt, + format: :json + end + + it 'returns a content with original filename, new link, and correct type.' do + expect(response.body).to match '\"alt\":\"doc_sample.txt\"' + expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads" + expect(response.body).to match '\"is_image\":false' + end + end + end +end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 06c703ecf7..89bb35de8f 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -7,44 +7,6 @@ describe ProjectsController do let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } - describe "POST #upload_image" do - before do - sign_in(user) - project.team << [user, :developer] - end - - context "without params['markdown_img']" do - it "returns an error" do - post(:upload_image, namespace_id: project.namespace.to_param, - id: project.to_param, format: :json) - expect(response.status).to eq(422) - end - end - - context "with invalid file" do - before do - post(:upload_image, namespace_id: project.namespace.to_param, - id: project.to_param, markdown_img: txt, format: :json) - end - - it "returns an error" do - expect(response.status).to eq(422) - end - end - - context "with valid file" do - before do - post(:upload_image, namespace_id: project.namespace.to_param, - id: project.to_param, markdown_img: jpg, format: :json) - end - - it "returns a content with original filename and new link." do - expect(response.body).to match "\"alt\":\"rails_sample\"" - expect(response.body).to match "\"url\":\"http://test.host/uploads/#{project.path_with_namespace}" - end - end - end - describe "POST #toggle_star" do it "toggles star if user is signed in" do sign_in(user) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 9d99b6e33c..99ff8a32ea 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -64,8 +64,9 @@ describe ApplicationHelper do project = create(:project) project.avatar = File.open(avatar_file_path) project.save! + avatar_url = "http://localhost/uploads/project/avatar/#{ project.id }/gitlab_logo.png" expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to eq( - "\"Gitlab" + "\"Gitlab" ) end @@ -75,8 +76,9 @@ describe ApplicationHelper do allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) + avatar_url = 'http://localhost' + namespace_project_avatar_path(project.namespace, project) expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match( - image_tag(namespace_project_avatar_path(project.namespace, project))) + image_tag(avatar_url)) end end @@ -192,10 +194,12 @@ describe ApplicationHelper do it 'sorts tags in a natural order' do # Stub repository.tag_names to make sure we get some valid testing data expect(@project.repository).to receive(:tag_names). - and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v1.0.9a']) + and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿', + 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2']) expect(options[1][1]). - to eq(['v3.1.4.2', 'v2.0', 'v1.0.10', 'v1.0.9a', 'v1.0.9']) + to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10', + 'v1.0.9', 'v1.0.9a']) end end diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb new file mode 100644 index 0000000000..f552310584 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Gitlab::BitbucketImport::ProjectCreator do + let(:user) { create(:user, bitbucket_access_token: "asdffg", bitbucket_access_token_secret: "sekret") } + let(:repo) { { + name: 'Vim', + slug: 'vim', + is_private: true, + owner: "asd"}.with_indifferent_access + } + let(:namespace){ create(:namespace) } + + it 'creates project' do + allow_any_instance_of(Project).to receive(:add_import_job) + + project_creator = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute + + expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git") + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + end +end diff --git a/spec/lib/gitlab/github/project_creator.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb similarity index 77% rename from spec/lib/gitlab/github/project_creator.rb rename to spec/lib/gitlab/github_import/project_creator_spec.rb index 3686ddbf17..8d594a112d 100644 --- a/spec/lib/gitlab/github/project_creator.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Github::ProjectCreator do +describe Gitlab::GithubImport::ProjectCreator do let(:user) { create(:user, github_access_token: "asdffg") } let(:repo) { OpenStruct.new( login: 'vim', @@ -15,9 +15,8 @@ describe Gitlab::Github::ProjectCreator do it 'creates project' do allow_any_instance_of(Project).to receive(:add_import_job) - project_creator = Gitlab::Github::ProjectCreator.new(repo, namespace, user) - project_creator.execute - project = Project.last + project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) diff --git a/spec/lib/gitlab/gitlab_import/project_creator.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb similarity index 91% rename from spec/lib/gitlab/gitlab_import/project_creator.rb rename to spec/lib/gitlab/gitlab_import/project_creator_spec.rb index e5d917830b..4c0d64ed13 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::GitlabImport::ProjectCreator do let(:user) { create(:user, gitlab_access_token: "asdffg") } - let(:repo) {{ + let(:repo) { { name: 'vim', path: 'vim', visibility_level: Gitlab::VisibilityLevel::PRIVATE, @@ -16,8 +16,7 @@ describe Gitlab::GitlabImport::ProjectCreator do allow_any_instance_of(Project).to receive(:add_import_job) project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user) - project_creator.execute - project = Project.last + project = project_creator.execute expect(project.import_url).to eq("https://oauth2:asdffg@gitlab.com/asd/vim.git") expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb new file mode 100644 index 0000000000..bbd5245ad3 --- /dev/null +++ b/spec/models/project_services/irker_service_spec.rb @@ -0,0 +1,103 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +require 'spec_helper' +require 'socket' +require 'json' + +describe IrkerService do + describe 'Associations' do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe 'Validations' do + before do + subject.active = true + subject.properties['recipients'] = _recipients + end + + context 'active' do + let(:_recipients) { nil } + it { should validate_presence_of :recipients } + end + + context 'too many recipients' do + let(:_recipients) { 'a b c d' } + it 'should add an error if there is too many recipients' do + subject.send :check_recipients_count + subject.errors.should_not be_blank + end + end + + context '3 recipients' do + let(:_recipients) { 'a b c' } + it 'should not add an error if there is 3 recipients' do + subject.send :check_recipients_count + subject.errors.should be_blank + end + end + end + + describe 'Execute' do + let(:irker) { IrkerService.new } + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + let(:recipients) { '#commits' } + let(:colorize_messages) { '1' } + + before do + irker.stub( + active: true, + project: project, + project_id: project.id, + service_hook: true, + properties: { + 'recipients' => recipients, + 'colorize_messages' => colorize_messages + } + ) + irker.settings = { + server_ip: 'localhost', + server_port: 6659, + max_channels: 3, + default_irc_uri: 'irc://chat.freenode.net/' + } + irker.valid? + @irker_server = TCPServer.new 'localhost', 6659 + end + + after do + @irker_server.close + end + + it 'should send valid JSON messages to an Irker listener' do + irker.execute(sample_data) + + conn = @irker_server.accept + conn.readlines.each do |line| + msg = JSON.load(line.chomp("\n")) + msg.keys.should match_array(['to', 'privmsg']) + if msg['to'].is_a?(String) + msg['to'].should == 'irc://chat.freenode.net/#commits' + else + msg['to'].should match_array(['irc://chat.freenode.net/#commits']) + end + end + conn.close + end + end +end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 90b385423f..8a75d8987a 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -36,6 +36,8 @@ describe SlackService do let(:project) { create(:project) } let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } + let(:username) { 'slack_username' } + let(:channel) { 'slack_channel' } before do slack.stub( @@ -53,5 +55,25 @@ describe SlackService do expect(WebMock).to have_requested(:post, webhook_url).once end + + it 'should use the username as an option for slack when configured' do + slack.stub(username: username) + expect(Slack::Notifier).to receive(:new). + with(webhook_url, username: username). + and_return( + double(:slack_service).as_null_object + ) + slack.execute(sample_data) + end + + it 'should use the channel as an option when it is configured' do + slack.stub(channel: channel) + expect(Slack::Notifier).to receive(:new). + with(webhook_url, channel: channel). + and_return( + double(:slack_service).as_null_object + ) + slack.execute(sample_data) + end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a9df6f137b..879a63dd9f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -326,4 +326,35 @@ describe Project do expect(project.avatar_type).to eq(['only images allowed']) end end + + describe :avatar_url do + subject { project.avatar_url } + + let(:project) { create(:project) } + + context 'When avatar file is uploaded' do + before do + project.update_columns(avatar: 'uploads/avatar.png') + allow(project.avatar).to receive(:present?) { true } + end + + let(:avatar_path) do + "/uploads/project/avatar/#{project.id}/uploads/avatar.png" + end + + it { should eq "http://localhost#{avatar_path}" } + end + + context 'When avatar file in git' do + before do + allow(project).to receive(:avatar_in_git) { true } + end + + let(:avatar_path) do + "/#{project.namespace.name}/#{project.path}/avatar" + end + + it { should eq "http://localhost#{avatar_path}" } + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8a98e5365f..b31acde606 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -539,4 +539,32 @@ describe User do expect(User.sort(nil).first).to eq(@user) end end + + describe "#contributed_projects_ids" do + + subject { create(:user) } + let!(:project1) { create(:project) } + let!(:project2) { create(:project, forked_from_project: project3) } + let!(:project3) { create(:project) } + let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) } + let!(:push_event) { create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject) } + let!(:merge_event) { create(:event, action: Event::CREATED, project: project3, target: merge_request, author: subject) } + + before do + project1.team << [subject, :master] + project2.team << [subject, :master] + end + + it "includes IDs for projects the user has pushed to" do + expect(subject.contributed_projects_ids).to include(project1.id) + end + + it "includes IDs for projects the user has had merge requests merged into" do + expect(subject.contributed_projects_ids).to include(project3.id) + end + + it "doesn't include IDs for unrelated projects" do + expect(subject.contributed_projects_ids).not_to include(project2.id) + end + end end diff --git a/spec/services/issues/bulk_update_context_spec.rb b/spec/services/issues/bulk_update_service_spec.rb similarity index 76% rename from spec/services/issues/bulk_update_context_spec.rb rename to spec/services/issues/bulk_update_service_spec.rb index eb867f78c5..504213e667 100644 --- a/spec/services/issues/bulk_update_context_spec.rb +++ b/spec/services/issues/bulk_update_service_spec.rb @@ -84,6 +84,25 @@ describe Issues::BulkUpdateService do expect(@project.issues.first.assignee).to eq(@new_assignee) } + it 'allows mass-unassigning' do + @project.issues.first.update_attribute(:assignee, @new_assignee) + expect(@project.issues.first.assignee).not_to be_nil + + @params[:update][:assignee_id] = -1 + + Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(@project.issues.first.assignee).to be_nil + end + + it 'does not unassign when assignee_id is not present' do + @project.issues.first.update_attribute(:assignee, @new_assignee) + expect(@project.issues.first.assignee).not_to be_nil + + @params[:update][:assignee_id] = '' + + Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(@project.issues.first.assignee).not_to be_nil + end end describe :update_milestone do diff --git a/spec/services/projects/image_service_spec.rb b/spec/services/projects/image_service_spec.rb deleted file mode 100644 index 23c4e227ae..0000000000 --- a/spec/services/projects/image_service_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'spec_helper' - -describe Projects::ImageService do - describe 'Image service' do - before do - @user = create :user - @project = create :project, creator_id: @user.id, namespace: @user.namespace - end - - context 'for valid gif file' do - before do - gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => gif }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("banana_sample") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("banana_sample.gif") } - end - - context 'for valid png file' do - before do - png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => png }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("dk") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("dk.png") } - end - - context 'for valid jpg file' do - before do - jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => jpg }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("rails_sample") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("rails_sample.jpg") } - end - - context 'for txt file' do - before do - txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => txt }, "http://test.example/") - end - - it { expect(@link_to_image).to be_nil } - end - end - - def upload_image(repository, params, root_url) - Projects::ImageService.new(repository, params, root_url).execute - end -end diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb new file mode 100644 index 0000000000..fc34b45648 --- /dev/null +++ b/spec/services/projects/upload_service_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe Projects::UploadService do + describe 'File service' do + before do + @user = create :user + @project = create :project, creator_id: @user.id, namespace: @user.namespace + end + + context 'for valid gif file' do + before do + gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') + @link_to_file = upload_file(@project.repository, gif) + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('banana_sample') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('banana_sample.gif') } + end + + context 'for valid png file' do + before do + png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', + 'image/png') + @link_to_file = upload_file(@project.repository, png) + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_value('dk') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('dk.png') } + end + + context 'for valid jpg file' do + before do + jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') + @link_to_file = upload_file(@project.repository, jpg) + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('rails_sample') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('rails_sample.jpg') } + end + + context 'for txt file' do + before do + txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') + @link_to_file = upload_file(@project.repository, txt) + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('doc_sample.txt') } + it { expect(@link_to_file['is_image']).to equal(false) } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('doc_sample.txt') } + end + end + + def upload_file(repository, file) + Projects::UploadService.new(repository, file).execute + end +end