diff options
136 files changed, 1489 insertions, 1034 deletions
diff --git a/CHANGELOG b/CHANGELOG index 6837037a367..8692f61fe36 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,15 +10,33 @@ v 8.6.0 (unreleased) - Return empty array instead of 404 when commit has no statuses in commit status API - Update documentation to reflect Guest role not being enforced on internal projects - Allow search for logged out users + - Don't show Issues/MRs from archived projects in Groups view + - Increase the notes polling timeout over time (Roberto Dip) + +v 8.5.4 + - Do not cache requests for badges (including builds badge) + +v 8.5.3 + - Flush repository caches before renaming projects + - Sort starred projects on dashboard based on last activity by default + - Show commit message in JIRA mention comment v 8.5.2 - Fix sidebar overlapping content when screen width was below 1200px - - Fix permissions for deprecated CI build status badge + - Don't repeat labels listed on Labels tab + - Bring the "branded appearance" feature from EE to CE - Fix error 500 when commenting on a commit + - Show days remaining instead of elapsed time for Milestone - Fix broken icons on installations with relative URL (Artem Sidorenko) + - Fix issue where tag list wasn't refreshed after deleting a tag - Fix import from gitlab.com (KazSawada) + - Improve implementation to check read access to forks and add pagination + - Don't show any "2FA required" message if it's not actually required - Fix help keyboard shortcut on relative URL setups (Artem Sidorenko) + - Update Rails to 4.2.5.2 + - Fix permissions for deprecated CI build status badge - Don't show "Welcome to GitLab" when the search didn't return any projects + - Add Todos documentation v 8.5.1 - Fix group projects styles @@ -33,7 +51,7 @@ v 8.5.1 - Fix error 500 when trying to mark an already done todo as "done" - Fix an issue where MRs weren't sortable - Issues can now be dragged & dropped into empty milestone lists. This is also - possible with MRs + possible with MRs - Changed padding & background color for highlighted notes - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) - Update sentry-raven gem to 0.15.6 @@ -50,7 +50,7 @@ gem "browser", '~> 1.0.0' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 8.2' +gem "gitlab_git", '~> 9.0' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes @@ -278,7 +278,7 @@ group :development, :test do gem 'teaspoon', '~> 1.0.0' gem 'teaspoon-jasmine', '~> 2.2.0' - gem 'spring', '~> 1.3.6' + gem 'spring', '~> 1.6.4' gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-spinach', '~> 1.0.0' gem 'spring-commands-teaspoon', '~> 0.0.2' diff --git a/Gemfile.lock b/Gemfile.lock index e048e2f5a56..bdf7dbfd157 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -357,7 +357,7 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.3.1) gemojione (~> 2.2, >= 2.2.1) - gitlab_git (8.2.0) + gitlab_git (9.0.0) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -763,7 +763,7 @@ GEM capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) - spring (1.3.6) + spring (1.6.4) spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-spinach (1.0.0) @@ -929,7 +929,7 @@ DEPENDENCIES github-markup (~> 1.3.1) gitlab-flowdock-git-hook (~> 1.0.1) gitlab_emoji (~> 0.3.0) - gitlab_git (~> 8.2) + gitlab_git (~> 9.0) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.1.0) @@ -1016,7 +1016,7 @@ DEPENDENCIES six (~> 0.2.0) slack-notifier (~> 1.2.0) spinach-rails (~> 0.2.1) - spring (~> 1.3.6) + spring (~> 1.6.4) spring-commands-rspec (~> 1.0.4) spring-commands-spinach (~> 1.0.0) spring-commands-teaspoon (~> 0.0.2) diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee index 4ec8531d580..61c2e4f698c 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js.coffee +++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee @@ -1,23 +1,30 @@ # Quick Submit behavior # -# When an input field with the `js-quick-submit` class receives a "Meta+Enter" -# (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, its parent form is -# submitted. +# When a child field of a form with a `js-quick-submit` class receives a +# "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form +# is submitted. # #= require extensions/jquery # # ### Example Markup # -# <form action="/foo"> -# <input type="text" class="js-quick-submit" /> -# <textarea class="js-quick-submit"></textarea> +# <form action="/foo" class="js-quick-submit"> +# <input type="text" /> +# <textarea></textarea> +# <input type="submit" value="Submit" /> # </form> # +isMac = -> + navigator.userAgent.match(/Macintosh/) + +keyCodeIs = (e, keyCode) -> + return false if (e.originalEvent && e.originalEvent.repeat) || e.repeat + return e.keyCode == keyCode + $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> - return if (e.originalEvent && e.originalEvent.repeat) || e.repeat - return unless e.keyCode == 13 # Enter + return unless keyCodeIs(e, 13) # Enter - if navigator.userAgent.match(/Macintosh/) + if isMac() return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) else return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) @@ -27,3 +34,22 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> $form = $(e.target).closest('form') $form.find('input[type=submit], button[type=submit]').disable() $form.submit() + +# If the user tabs to a submit button on a `js-quick-submit` form, display a +# tooltip to let them know they could've used the hotkey +$(document).on 'keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', (e) -> + return unless keyCodeIs(e, 9) # Tab + + if isMac() + title = "You can also press ⌘-Enter" + else + title = "You can also press Ctrl-Enter" + + $this = $(@) + $this.tooltip( + container: 'body' + html: 'true' + placement: 'auto top' + title: title + trigger: 'manual' + ).tooltip('show').one('blur', -> $this.tooltip('hide')) diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee deleted file mode 100644 index 62143e66cfe..00000000000 --- a/app/assets/javascripts/dashboard.js.coffee +++ /dev/null @@ -1,31 +0,0 @@ -@Dashboard = - init: -> - $(".projects-list-filter").off('keyup') - this.initSearch() - - initSearch: -> - @timer = null - $(".projects-list-filter").on('keyup', -> - clearTimeout(@timer) - @timer = setTimeout(Dashboard.filterResults, 500) - ) - - filterResults: => - $('.projects-list-holder').fadeTo(250, 0.5) - - form = null - form = $("form#project-filter-form") - search = $(".projects-list-filter").val() - project_filter_url = form.attr('action') + '?' + form.serialize() - - $.ajax - type: "GET" - url: form.attr('action') - data: form.serialize() - complete: -> - $('.projects-list-holder').fadeTo(250, 1) - success: (data) -> - $('.projects-list-holder').replaceWith(data.html) - # Change url so if user reload a page - search results are saved - history.replaceState {page: project_filter_url}, document.title, project_filter_url - dataType: "json" diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 89f1993797f..d7feb5d5c87 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -16,8 +16,6 @@ class Dispatcher shortcut_handler = null switch page - when 'explore:projects:index', 'explore:projects:starred', 'explore:projects:trending' - Dashboard.init() when 'projects:issues:index' Issues.init() shortcut_handler = new ShortcutsNavigation() @@ -59,8 +57,6 @@ class Dispatcher when 'projects:merge_requests:index' shortcut_handler = new ShortcutsNavigation() MergeRequests.init() - when 'dashboard:show', 'root:show' - Dashboard.init() when 'dashboard:activity' new Activities() when 'dashboard:projects:starred' @@ -107,9 +103,6 @@ class Dispatcher new ProjectFork() when 'projects:artifacts:browse' new BuildArtifacts() - when 'users:show' - new User() - new Activities() switch path.first() when 'admin' diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 3347ab65c90..c95ead22e6c 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -16,11 +16,13 @@ class @Notes @view = view @noteable_url = document.URL @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge") + @basePollingInterval = 15000 + @maxPollingSteps = 4 - @initRefresh() - @setupMainTargetNoteForm() @cleanBinding() @addBinding() + @setPollingInterval() + @setupMainTargetNoteForm() @initTaskList() addBinding: -> @@ -37,7 +39,7 @@ class @Notes # Reopen and close actions for Issue/MR combined with note form submit $(document).on "click", ".js-comment-button", @updateCloseButton - $(document).on "keyup", ".js-note-text", @updateTargetButtons + $(document).on "keyup input", ".js-note-text", @updateTargetButtons # remove a note (in general) $(document).on "click", ".js-note-delete", @removeNote @@ -91,9 +93,11 @@ class @Notes clearInterval(Notes.interval) Notes.interval = setInterval => @refresh() - , 15000 + , @pollingInterval refresh: -> + return if @refreshing is true + refreshing = true if not document.hidden and document.URL.indexOf(@noteable_url) is 0 @getContent() @@ -105,12 +109,31 @@ class @Notes success: (data) => notes = data.notes @last_fetched_at = data.last_fetched_at + @setPollingInterval(data.notes.length) $.each notes, (i, note) => if note.discussion_with_diff_html? @renderDiscussionNote(note) else @renderNote(note) + always: => + @refreshing = false + + ### + Increase @pollingInterval up to 120 seconds on every function call, + if `shouldReset` has a truthy value, 'null' or 'undefined' the variable + will reset to @basePollingInterval. + + Note: this function is used to gradually increase the polling interval + if there aren't new notes coming from the server + ### + setPollingInterval: (shouldReset = true) -> + nthInterval = @basePollingInterval * Math.pow(2, @maxPollingSteps - 1) + if shouldReset + @pollingInterval = @basePollingInterval + else if @pollingInterval < nthInterval + @pollingInterval *= 2 + @initRefresh() ### Render note in main comments area. diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee index d639303aed3..0ff83b7f0c8 100644 --- a/app/assets/javascripts/pager.js.coffee +++ b/app/assets/javascripts/pager.js.coffee @@ -1,6 +1,7 @@ @Pager = init: (@limit = 0, preload, @disable = false) -> - @loading = $(".loading") + @loading = $('.loading').first() + if preload @offset = 0 @getOld() diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index 69d590a7533..9110b732adc 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -48,7 +48,7 @@ class @Profile $filename.text($filename.data('label')) $('.js-upload-user-avatar').on 'click', -> - $('.edit_user').submit() + $('.edit-user').submit() $avatarInput.on "change", -> form = $(this).closest("form") @@ -63,3 +63,11 @@ class @Profile fileData = reader.readAsDataURL(this.files[0]) +$ -> + # Extract the SSH Key title from its comment + $(document).on 'focusout.ssh_key', '#key_key', -> + $title = $('#key_title') + comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/) + + if comment && comment.length > 1 && $title.val() == '' + $title.val(comment[1]).change() diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee index eab34be652a..ed5206368ce 100644 --- a/app/assets/javascripts/projects_list.js.coffee +++ b/app/assets/javascripts/projects_list.js.coffee @@ -1,26 +1,31 @@ -class @ProjectsList - constructor: -> - $(".projects-list .js-expand").on 'click', (e) -> - e.preventDefault() - list = $(this).closest('.projects-list') +@ProjectsList = + init: -> + $(".projects-list-filter").off('keyup') + this.initSearch() - $("#filter_projects").on 'keyup', -> - ProjectsList.filter_results($("#filter_projects")) + initSearch: -> + @timer = null + $(".projects-list-filter").on('keyup', -> + clearTimeout(@timer) + @timer = setTimeout(ProjectsList.filterResults, 500) + ) - @filter_results: ($element) -> - terms = $element.val() - filterSelector = $element.data('filter-selector') || 'span.filter-title' + filterResults: => + $('.projects-list-holder').fadeTo(250, 0.5) - if not terms - $(".projects-list li").show() - $('.gl-pagination').show() - else - $(".projects-list li").each (index) -> - $this = $(this) - name = $this.find(filterSelector).text() + form = null + form = $("form#project-filter-form") + search = $(".projects-list-filter").val() + project_filter_url = form.attr('action') + '?' + form.serialize() - if name.toLowerCase().indexOf(terms.toLowerCase()) == -1 - $this.hide() - else - $this.show() - $('.gl-pagination').hide() + $.ajax + type: "GET" + url: form.attr('action') + data: form.serialize() + complete: -> + $('.projects-list-holder').fadeTo(250, 1) + success: (data) -> + $('.projects-list-holder').replaceWith(data.html) + # Change url so if user reload a page - search results are saved + history.replaceState {page: project_filter_url}, document.title, project_filter_url + dataType: "json" diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee index ec4271b092c..2882a90d118 100644 --- a/app/assets/javascripts/user.js.coffee +++ b/app/assets/javascripts/user.js.coffee @@ -1,10 +1,17 @@ class @User - constructor: -> + constructor: (@opts) -> $('.profile-groups-avatars').tooltip("placement": "top") - new ProjectsList() + + @initTabs() $('.hide-project-limit-message').on 'click', (e) -> path = '/' $.cookie('hide_project_limit_message', 'false', { path: path }) $(@).parents('.project-limit-message').remove() e.preventDefault() + + initTabs: -> + new UserTabs( + parentEl: '.user-profile' + action: @opts.action + ) diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee new file mode 100644 index 00000000000..09b7eec9104 --- /dev/null +++ b/app/assets/javascripts/user_tabs.js.coffee @@ -0,0 +1,146 @@ +# UserTabs +# +# Handles persisting and restoring the current tab selection and lazily-loading +# content on the Users#show page. +# +# ### Example Markup +# +# <ul class="nav-links"> +# <li class="activity-tab active"> +# <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username"> +# Activity +# </a> +# </li> +# <li class="groups-tab"> +# <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups"> +# Groups +# </a> +# </li> +# <li class="contributed-tab"> +# <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed"> +# Contributed projects +# </a> +# </li> +# <li class="projects-tab"> +# <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects"> +# Personal projects +# </a> +# </li> +# </ul> +# +# <div class="tab-content"> +# <div class="tab-pane" id="activity"> +# Activity Content +# </div> +# <div class="tab-pane" id="groups"> +# Groups Content +# </div> +# <div class="tab-pane" id="contributed"> +# Contributed projects content +# </div> +# <div class="tab-pane" id="projects"> +# Projects content +# </div> +# </div> +# +# <div class="loading-status"> +# <div class="loading"> +# Loading Animation +# </div> +# </div> +# +class @UserTabs + constructor: (opts) -> + { + @action = 'activity' + @defaultAction = 'activity' + @parentEl = $(document) + } = opts + + # Make jQuery object if selector is provided + @parentEl = $(@parentEl) if typeof @parentEl is 'string' + + # Store the `location` object, allowing for easier stubbing in tests + @_location = location + + # Set tab states + @loaded = {} + for item in @parentEl.find('.nav-links a') + @loaded[$(item).attr 'data-action'] = false + + # Actions + @actions = Object.keys @loaded + + @bindEvents() + + # Set active tab + @action = @defaultAction if @action is 'show' + @activateTab(@action) + + bindEvents: -> + # Toggle event listeners + @parentEl + .off 'shown.bs.tab', '.nav-links a[data-toggle="tab"]' + .on 'shown.bs.tab', '.nav-links a[data-toggle="tab"]', @tabShown + + tabShown: (event) => + $target = $(event.target) + action = $target.data('action') + source = $target.attr('href') + + @setTab(source, action) + @setCurrentAction(action) + + activateTab: (action) -> + @parentEl.find(".nav-links .#{action}-tab a").tab('show') + + setTab: (source, action) -> + return if @loaded[action] is true + + if action is 'activity' + @loadActivities(source) + + if action in ['groups', 'contributed', 'projects'] + @loadTab(source, action) + + loadTab: (source, action) -> + $.ajax + beforeSend: => @toggleLoading(true) + complete: => @toggleLoading(false) + dataType: 'json' + type: 'GET' + url: "#{source}.json" + success: (data) => + tabSelector = 'div#' + action + @parentEl.find(tabSelector).html(data.html) + @loaded[action] = true + + loadActivities: (source) -> + return if @loaded['activity'] is true + + $calendarWrap = @parentEl.find('.user-calendar') + $calendarWrap.load($calendarWrap.data('href')) + + new Activities() + @loaded['activity'] = true + + toggleLoading: (status) -> + @parentEl.find('.loading-status .loading').toggle(status) + + setCurrentAction: (action) -> + # Remove possible actions from URL + regExp = new RegExp('\/(' + @actions.join('|') + ')(\.html)?\/?$') + new_state = @_location.pathname + new_state = new_state.replace(/\/+$/, "") # remove trailing slashes + new_state = new_state.replace(regExp, '') + + # Append the new action if we're on a tab other than 'activity' + unless action == @defaultAction + new_state += "/#{action}" + + # Ensure parameters and hash come along for the ride + new_state += @_location.search + @_location.hash + + history.replaceState {turbolinks: true, url: new_state}, document.title, new_state + + new_state diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 50aa170d24c..8d475137b03 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -143,6 +143,19 @@ } } +.btn-transparent { + color: $btn-transparent-color; + background-color: transparent; + border: 0; + + &:hover, + &:active, + &:focus { + background-color: transparent; + box-shadow: none; + } +} + .btn-block { width: 100%; margin: 0; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 9e8506b2a81..1d3c71932a8 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -6,11 +6,15 @@ .cdark { color: #444 } /** COMMON CLASSES **/ +.prepend-top-0 { margin-top: 0; } +.prepend-top-5 { margin-top: 5px; } .prepend-top-10 { margin-top:10px } .prepend-top-default { margin-top: $gl-padding !important; } .prepend-top-20 { margin-top:20px } .prepend-left-10 { margin-left:10px } +.prepend-left-default { margin-left:$gl-padding } .prepend-left-20 { margin-left:20px } +.append-right-5 { margin-right: 5px } .append-right-10 { margin-right:10px } .append-right-20 { margin-right:20px } .append-bottom-10 { margin-bottom:10px } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index d097e4d32f7..6c08005812e 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -28,15 +28,15 @@ input[type='search'].search-input { } &.search-input:-moz-placeholder { /* Firefox 18- */ - text-align: center; + text-align: center; } &.search-input::-moz-placeholder { /* Firefox 19+ */ - text-align: center; + text-align: center; } - &.search-input:-ms-input-placeholder { - text-align: center; + &.search-input:-ms-input-placeholder { + text-align: center; } } @@ -69,6 +69,10 @@ label { &.inline-label { margin: 0; } + + &.label-light { + font-weight: 600; + } } .inline-input-group { diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index b6a781f79de..bfec0911b3c 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -3,6 +3,7 @@ * */ .well-list { + position: relative; margin: 0; padding: 0; list-style: none; diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index d24faa897a1..7de874c8bcd 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -79,6 +79,10 @@ > .dropdown { margin-right: $gl-padding-top; display: inline-block; + + &:last-child { + margin-right: 0; + } } > .btn { diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 3e709244879..1e9959429cd 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -167,12 +167,6 @@ } } -.alert-help { - background-color: $background-color; - border: 1px solid $border-color; - color: $gl-gray; -} - // Typography ================================================================= .text-primary, diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8d8f41287da..48570abff49 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -196,7 +196,7 @@ body { h1, h2, h3, h4, h5, h6 { color: $gl-header-color; - font-weight: 500; + font-weight: 600; } /** CODE **/ diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 7834cb0bfa5..6fc62f7f201 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -34,6 +34,12 @@ $error-exclamation-point: #E62958; $border-radius-default: 3px; $list-title-color: #333333; $list-text-color: #555555; +$profile-settings-link-color: $md-link-color; + +$btn-transparent-color: #8F8F8F; + +$ssh-key-icon-color: #8F8F8F; +$ssh-key-icon-size: 18px; /* * Color schema @@ -92,6 +98,9 @@ $border-red-light: #E52C5A; $border-red-normal: #D22852; $border-red-dark: #CA264F; +$help-well-bg: #FAFAFA; +$help-well-border: #E5E5E5; + /* header */ $light-grey-header: #faf9f9; diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss index cc273f55222..94fbbef3c77 100644 --- a/app/assets/stylesheets/pages/notifications.scss +++ b/app/assets/stylesheets/pages/notifications.scss @@ -1,16 +1,18 @@ -.global-notifications-form .level-title { - font-size: 15px; - color: #333; - font-weight: bold; +.notification-list-item { + line-height: 34px; } -.notification-icon-holder { - width: 20px; - float: left; +.notification { + position: relative; + top: 1px; + + > .fa { + font-size: 18px; + } } .ns-part { - color: $gl-primary; + color: $gl-text-green; } .ns-watch { diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index de4d9fd80fa..4826b994e37 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -5,12 +5,24 @@ } } -.profile-avatar-form-option { - hr { - margin: 10px 0; +.avatar-image { + @media (min-width: $screen-sm-min) { + float: left; + margin-bottom: 0; } } +.avatar-file-name { + position: relative; + top: 2px; + display: inline-block; +} + +.account-btn-link, +.profile-settings-sidebar a { + color: $profile-settings-link-color; +} + .oauth-buttons { .btn-group { margin-right: 10px; @@ -42,6 +54,18 @@ } } +.account-well { + padding: 10px 10px; + background-color: $help-well-bg; + border: 1px solid $help-well-border; + border-radius: $border-radius-base; + + ul { + padding-left: 20px; + margin-bottom: 0; + } +} + .calendar-hint { margin-top: -12px; float: right; @@ -79,6 +103,13 @@ margin: auto; } +.user-avatar-button { + .file-name { + display: inline-block; + padding-left: 10px; + } +} + .modal-profile-crop { .modal-dialog { width: 500px; @@ -114,3 +145,33 @@ width: auto; } } + +.key-list-item { + .key-list-item-info { + @media (min-width: $screen-sm-min) { + float: left; + } + } + + .description { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } +} + +.key-icon { + color: $ssh-key-icon-color; + font-size: $ssh-key-icon-size; + line-height: 42px; +} + +.key-created-at { + line-height: 42px; +} + +.profile-settings-content { + a { + color: $profile-settings-link-color; + } +} diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 0dc5a905f99..479c3c16d46 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -8,6 +8,7 @@ .badge.todos-pending-count { background-color: #7f8fa4; margin-top: -5px; + font-weight: normal; } } } diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb index 5b098628557..ef8e74a4641 100644 --- a/app/controllers/concerns/issues_action.rb +++ b/app/controllers/concerns/issues_action.rb @@ -2,7 +2,7 @@ module IssuesAction extend ActiveSupport::Concern def issues - @issues = get_issues_collection + @issues = get_issues_collection.non_archived @issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE) @issues = @issues.preload(:author, :project) diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb index f6de696e84d..9c49596bd0b 100644 --- a/app/controllers/concerns/merge_requests_action.rb +++ b/app/controllers/concerns/merge_requests_action.rb @@ -2,7 +2,7 @@ module MergeRequestsAction extend ActiveSupport::Concern def merge_requests - @merge_requests = get_merge_requests_collection + @merge_requests = get_merge_requests_collection.non_archived @merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE) @merge_requests = @merge_requests.preload(:author, :target_project) diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index aeafbd30143..dc880b634e5 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -31,7 +31,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController end def starred - @projects = current_user.starred_projects + @projects = current_user.starred_projects.sorted_by_activity @projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.sort(@sort = params[:sort]) diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index f3224148fda..b88c080352b 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -3,23 +3,21 @@ class Profiles::KeysController < Profiles::ApplicationController def index @keys = current_user.keys + @key = Key.new end def show @key = current_user.keys.find(params[:id]) end - def new - @key = current_user.keys.new - end - def create @key = current_user.keys.new(key_params) if @key.save redirect_to profile_key_path(@key) else - render 'new' + @keys = current_user.keys.select(&:persisted?) + render :index end end diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index a4dd94b941c..dc9c96df003 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -1,4 +1,6 @@ class Projects::BadgesController < Projects::ApplicationController + before_action :set_no_cache + def build respond_to do |format| format.html { render_404 } @@ -8,4 +10,15 @@ class Projects::BadgesController < Projects::ApplicationController end end end + + private + + def set_no_cache + expires_now + + # Add some deprecated headers for older agents + # + response.headers['Pragma'] = 'no-cache' + response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' + end end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 97d31a4229a..576fa3cedb2 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -3,6 +3,7 @@ # Not to be confused with CommitsController, plural. class Projects::CommitController < Projects::ApplicationController include CreatesCommit + include DiffHelper # Authorize before_action :require_non_empty_project @@ -100,12 +101,10 @@ class Projects::CommitController < Projects::ApplicationController def define_show_vars return git_not_found! unless commit - if params[:w].to_i == 1 - @diffs = commit.diffs({ ignore_whitespace_change: true }) - else - @diffs = commit.diffs - end + opts = diff_options + opts[:ignore_whitespace_change] = true if params[:format] == 'diff' + @diffs = commit.diffs(opts) @diff_refs = [commit.parent || commit, commit] @notes_count = commit.notes.count diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index dc5d217f3e4..671d5c23024 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -1,6 +1,8 @@ require 'addressable/uri' class Projects::CompareController < Projects::ApplicationController + include DiffHelper + # Authorize before_action :require_non_empty_project before_action :authorize_download_code! @@ -11,16 +13,14 @@ class Projects::CompareController < Projects::ApplicationController end def show - diff_options = { ignore_whitespace_change: true } if params[:w] == '1' - - compare_result = CompareService.new. + compare = CompareService.new. execute(@project, @head_ref, @project, @base_ref, diff_options) - if compare_result - @commits = Commit.decorate(compare_result.commits, @project) - @diffs = compare_result.diffs + if compare + @commits = Commit.decorate(compare.commits, @project) @commit = @project.commit(@head_ref) @base_commit = @project.merge_base_commit(@base_ref, @head_ref) + @diffs = compare.diffs(diff_options) @diff_refs = [@base_commit, @commit] @line_notes = [] end diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index a0835c9aad0..7b202f3862f 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -6,20 +6,24 @@ class Projects::ForksController < Projects::ApplicationController def index base_query = project.forks.includes(:creator) - @forks = if current_user - base_query.where('projects.visibility_level IN (?) OR projects.id IN (?)', - Project.public_and_internal_levels, - current_user.authorized_projects.pluck(:id)) - else - base_query.where('projects.visibility_level = ?', Project::PUBLIC) - end - + @forks = base_query.merge(ProjectsFinder.new.execute(current_user)) @total_forks_count = base_query.size @private_forks_count = @total_forks_count - @forks.size @public_forks_count = @total_forks_count - @private_forks_count @sort = params[:sort] || 'id_desc' + @forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present? @forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE) + + respond_to do |format| + format.html + + format.json do + render json: { + html: view_to_html_string("projects/forks/_projects", projects: @forks) + } + end + end end def new diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5fe21694605..03ba289eb94 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -1,4 +1,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController + include DiffHelper + before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check, @@ -111,7 +113,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commits = @merge_request.compare_commits.reverse @commit = @merge_request.last_commit @base_commit = @merge_request.diff_base_commit - @diffs = @merge_request.compare_diffs + @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @ci_commit = @merge_request.ci_commit @statuses = @ci_commit.statuses if @ci_commit diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 626213c6728..4b1cf242885 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,13 +3,6 @@ class UsersController < ApplicationController before_action :set_user def show - @contributed_projects = contributed_projects.joined(@user).reject(&:forked?) - - @projects = PersonalProjectsFinder.new(@user).execute(current_user) - @projects = @projects.page(params[:page]).per(PER_PAGE) - - @groups = @user.groups.order_id_desc - respond_to do |format| format.html @@ -25,6 +18,45 @@ class UsersController < ApplicationController end end + def groups + load_groups + + respond_to do |format| + format.html { render 'show' } + format.json do + render json: { + html: view_to_html_string("shared/groups/_list", groups: @groups) + } + end + end + end + + def projects + load_projects + + respond_to do |format| + format.html { render 'show' } + format.json do + render json: { + html: view_to_html_string("shared/projects/_list", projects: @projects, remote: true) + } + end + end + end + + def contributed + load_contributed_projects + + respond_to do |format| + format.html { render 'show' } + format.json do + render json: { + html: view_to_html_string("shared/projects/_list", projects: @contributed_projects) + } + end + end + end + def calendar calendar = contributions_calendar @timestamps = calendar.timestamps @@ -69,6 +101,20 @@ class UsersController < ApplicationController limit_recent(20, params[:offset]) end + def load_projects + @projects = + PersonalProjectsFinder.new(@user).execute(current_user) + .page(params[:page]).per(PER_PAGE) + end + + def load_contributed_projects + @contributed_projects = contributed_projects.joined(@user) + end + + def load_groups + @groups = @user.groups.order_id_desc + end + def projects_for_current_user ProjectsFinder.new.execute(current_user) end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index d76db867c5a..ff32e834499 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -12,40 +12,20 @@ module DiffHelper params[:view] == 'parallel' ? 'parallel' : 'inline' end - def allowed_diff_size - if diff_hard_limit_enabled? - Commit::DIFF_HARD_LIMIT_FILES - else - Commit::DIFF_SAFE_FILES - end + def diff_hard_limit_enabled? + params[:force_show_diff].present? end - def allowed_diff_lines + def diff_options + options = { ignore_whitespace_change: params[:w] == '1' } if diff_hard_limit_enabled? - Commit::DIFF_HARD_LIMIT_LINES - else - Commit::DIFF_SAFE_LINES + options.merge!(Commit.max_diff_options) end + options end def safe_diff_files(diffs, diff_refs) - lines = 0 - safe_files = [] - diffs.first(allowed_diff_size).each do |diff| - lines += diff.diff.lines.count - break if lines > allowed_diff_lines - safe_files << Gitlab::Diff::File.new(diff, diff_refs) - end - safe_files - end - - def diff_hard_limit_enabled? - # Enabling hard limit allows user to see more diff information - if params[:force_show_diff].present? - true - else - false - end + diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) } end def generate_line_code(file_path, line) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 84c6d0883b0..ab3ef454e1c 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -10,6 +10,15 @@ module IconsHelper options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options) end + def audit_icon(names, options = {}) + case names + when "standard" + names = "key" + end + + options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options) + end + def spinner(text = nil, visible = false) css_class = 'loading' css_class << ' hide' unless visible @@ -37,7 +46,7 @@ module IconsHelper else # Gitlab::VisibilityLevel::PUBLIC 'globe' end - + name << " fw" if fw icon(name) diff --git a/app/models/commit.rb b/app/models/commit.rb index 3224f5457f0..ce0b85d50cf 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -12,12 +12,7 @@ class Commit attr_accessor :project - # Safe amount of changes (files and lines) in one commit to render - # Used to prevent 500 error on huge commits by suppressing diff - # - # User can force display of diff above this size - DIFF_SAFE_FILES = 100 unless defined?(DIFF_SAFE_FILES) - DIFF_SAFE_LINES = 5000 unless defined?(DIFF_SAFE_LINES) + DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] # Commits above this size will not be rendered in HTML DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES) @@ -36,13 +31,20 @@ class Commit # Calculate number of lines to render for diffs def diff_line_count(diffs) - diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } + diffs.reduce(0) { |sum, d| sum + Gitlab::Git::Util.count_lines(d.diff) } end # Truncate sha to 8 characters def truncate_sha(sha) sha[0..7] end + + def max_diff_options + { + max_files: DIFF_HARD_LIMIT_FILES, + max_lines: DIFF_HARD_LIMIT_LINES, + } + end end attr_accessor :raw diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index e5f089fb8a0..286d6655861 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -38,6 +38,7 @@ module Issuable scope :join_project, -> { joins(:project) } scope :references_project, -> { references(:project) } + scope :non_archived, -> { join_project.merge(Project.non_archived) } delegate :name, :email, diff --git a/app/models/label.rb b/app/models/label.rb index 07a1db4abe5..c34f4e4ba60 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -27,6 +27,7 @@ class Label < ActiveRecord::Base belongs_to :project has_many :label_links, dependent: :destroy has_many :issues, through: :label_links, source: :target, source_type: 'Issue' + has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' validates :color, color: true, allow_blank: false validates :project, presence: true, unless: Proc.new { |service| service.template? } @@ -90,6 +91,10 @@ class Label < ActiveRecord::Base issues.closed.count end + def open_merge_requests_count + merge_requests.opened.count + end + def template? template end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1543ef311d7..025b522cf66 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -48,7 +48,7 @@ class MergeRequest < ActiveRecord::Base after_create :create_merge_request_diff after_update :update_merge_request_diff - delegate :commits, :diffs, :diffs_no_whitespace, to: :merge_request_diff, prefix: nil + delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests @@ -56,8 +56,7 @@ class MergeRequest < ActiveRecord::Base # Temporary fields to store compare vars # when creating new merge request - attr_accessor :can_be_created, :compare_failed, - :compare_commits, :compare_diffs + attr_accessor :can_be_created, :compare_commits, :compare state_machine :state, initial: :opened do event :close do @@ -182,6 +181,10 @@ class MergeRequest < ActiveRecord::Base merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end + def diff_size + merge_request_diff.size + end + def diff_base_commit if merge_request_diff merge_request_diff.base_commit diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index c95179d6046..df08d3a6dfb 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -19,14 +19,15 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 500 - attr_reader :commits, :diffs, :diffs_no_whitespace - belongs_to :merge_request delegate :target_branch, :source_branch, to: :merge_request, prefix: nil state_machine :state, initial: :empty do state :collected + state :overflow + # Deprecated states: these are no longer used but these values may still occur + # in the database. state :timeout state :overflow_commits_safe_size state :overflow_diff_files_limit @@ -43,19 +44,23 @@ class MergeRequestDiff < ActiveRecord::Base reload_diffs end - def diffs - @diffs ||= (load_diffs(st_diffs) || []) + def size + real_size.presence || diffs.size end - def diffs_no_whitespace - compare_result = Gitlab::CompareResult.new( - Gitlab::Git::Compare.new( - self.repository.raw_repository, - self.target_branch, - self.source_sha, - ), { ignore_whitespace_change: true } - ) - @diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs)) + def diffs(options={}) + if options[:ignore_whitespace_change] + @diffs_no_whitespace ||= begin + compare = Gitlab::Git::Compare.new( + self.repository.raw_repository, + self.target_branch, + self.source_sha, + ) + compare.diffs(options) + end + else + @diffs ||= load_diffs(st_diffs, options) + end end def commits @@ -94,16 +99,18 @@ class MergeRequestDiff < ActiveRecord::Base end end - def load_diffs(raw) - if raw.respond_to?(:map) - raw.map { |hash| Gitlab::Git::Diff.new(hash) } + def load_diffs(raw, options) + if raw.respond_to?(:each) + Gitlab::Git::DiffCollection.new(raw, options) + else + Gitlab::Git::DiffCollection.new([]) end end # Collect array of Git::Commit objects # between target and source branches def unmerged_commits - commits = compare_result.commits + commits = compare.commits if commits.present? commits = Commit.decorate(commits, merge_request.source_project). @@ -133,27 +140,21 @@ class MergeRequestDiff < ActiveRecord::Base if commits.size.zero? self.state = :empty - elsif commits.size > COMMITS_SAFE_SIZE - self.state = :overflow_commits_safe_size else - new_diffs = unmerged_diffs - end + diff_collection = unmerged_diffs - if new_diffs.any? - if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES - self.state = :overflow_diff_files_limit - new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES) + if diff_collection.overflow? + # Set our state to 'overflow' to make the #empty? and #collected? + # methods (generated by StateMachine) return false. + self.state = :overflow end - if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES - self.state = :overflow_diff_lines_limit - new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES) - end - end + self.real_size = diff_collection.real_size - if new_diffs.present? - new_diffs = dump_commits(new_diffs) - self.state = :collected + if diff_collection.any? + new_diffs = dump_diffs(diff_collection) + self.state = :collected + end end self.st_diffs = new_diffs @@ -166,10 +167,7 @@ class MergeRequestDiff < ActiveRecord::Base # Collect array of Git::Diff objects # between target and source branches def unmerged_diffs - compare_result.diffs || [] - rescue Gitlab::Git::Diff::TimeoutError - self.state = :timeout - [] + compare.diffs(Commit.max_diff_options) end def repository @@ -181,18 +179,16 @@ class MergeRequestDiff < ActiveRecord::Base source_commit.try(:sha) end - def compare_result - @compare_result ||= + def compare + @compare ||= begin # Update ref for merge request merge_request.fetch_ref - Gitlab::CompareResult.new( - Gitlab::Git::Compare.new( - self.repository.raw_repository, - self.target_branch, - self.source_sha - ) + Gitlab::Git::Compare.new( + self.repository.raw_repository, + self.target_branch, + self.source_sha ) end end diff --git a/app/models/note.rb b/app/models/note.rb index d287e0f3c6d..1a7b2ba6d42 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -131,9 +131,11 @@ class Note < ActiveRecord::Base end def find_diff - return nil unless noteable && noteable.diffs.present? + return nil unless noteable + return @diff if defined?(@diff) - @diff ||= noteable.diffs.find do |d| + # Don't use ||= because nil is a valid value for @diff + @diff = noteable.diffs(Commit.max_diff_options).find do |d| Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path end end @@ -165,20 +167,16 @@ class Note < ActiveRecord::Base def active? return true unless self.diff return false unless noteable + return @active if defined?(@active) - noteable.diffs.each do |mr_diff| - next unless mr_diff.new_path == self.diff.new_path + diffs = noteable.diffs(Commit.max_diff_options) + notable_diff = diffs.find { |d| d.new_path == self.diff.new_path } - lines = Gitlab::Diff::Parser.new.parse(mr_diff.diff.lines.to_a) + return @active = false if notable_diff.nil? - lines.each do |line| - if line.text == diff_line - return true - end - end - end - - false + parsed_lines = Gitlab::Diff::Parser.new.parse(notable_diff.diff.each_line) + # We cannot use ||= because @active may be false + @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line } end def outdated? @@ -263,7 +261,7 @@ class Note < ActiveRecord::Base end def diff_lines - @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines) + @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line) end def highlighted_diff_lines diff --git a/app/models/project.rb b/app/models/project.rb index 6f5d592755a..148eab692ff 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -278,7 +278,7 @@ class Project < ActiveRecord::Base end def search_by_title(query) - where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%") + non_archived.where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%") end def find_with_namespace(id) @@ -711,6 +711,8 @@ class Project < ActiveRecord::Base old_path_with_namespace = File.join(namespace_dir, path_was) new_path_with_namespace = File.join(namespace_dir, path) + expire_caches_before_rename(old_path_with_namespace) + if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) # If repository moved successfully we need to send update instructions to users. # However we cannot allow rollback since we moved repository @@ -739,6 +741,22 @@ class Project < ActiveRecord::Base Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path) end + # Expires various caches before a project is renamed. + def expire_caches_before_rename(old_path) + repo = Repository.new(old_path, self) + wiki = Repository.new("#{old_path}.wiki", self) + + if repo.exists? + repo.expire_cache + repo.expire_emptiness_caches + end + + if wiki.exists? + wiki.expire_cache + wiki.expire_emptiness_caches + end + end + def hook_attrs { name: name, diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index f6571fc063e..aba37921c09 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -108,7 +108,8 @@ class JiraService < IssueTrackerService }, entity: { name: noteable_name.humanize.downcase, - url: entity_url + url: entity_url, + title: noteable.title } } @@ -196,10 +197,11 @@ class JiraService < IssueTrackerService user_url = data[:user][:url] entity_name = data[:entity][:name] entity_url = data[:entity][:url] + entity_title = data[:entity][:title] project_name = data[:project][:name] message = { - body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]." + body: %Q{[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'} } unless existing_comment?(issue_name, message[:body]) diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index ec581658fc1..e2bccbdbcc3 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -1,7 +1,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories -# and return Gitlab::CompareResult object that responds to commits and diffs +# and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService def execute(source_project, source_branch, target_project, target_branch, diff_options = {}) source_commit = source_project.commit(source_branch) @@ -20,12 +20,10 @@ class CompareService ) end - Gitlab::CompareResult.new( - Gitlab::Git::Compare.new( - target_project.repository.raw_repository, - target_branch, - source_sha, - ), diff_options + Gitlab::Git::Compare.new( + target_project.repository.raw_repository, + target_branch, + source_sha, ) end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index c0700d953dd..954746a39a5 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -5,9 +5,7 @@ module MergeRequests # Set MR attributes merge_request.can_be_created = false - merge_request.compare_failed = false merge_request.compare_commits = [] - merge_request.compare_diffs = [] merge_request.source_project = project unless merge_request.source_project merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_branch ||= merge_request.target_project.default_branch @@ -21,35 +19,23 @@ module MergeRequests return build_failed(merge_request, message) end - compare_result = CompareService.new.execute( + compare = CompareService.new.execute( merge_request.source_project, merge_request.source_branch, merge_request.target_project, merge_request.target_branch, ) - commits = compare_result.commits + commits = compare.commits # At this point we decide if merge request can be created # If we have at least one commit to merge -> creation allowed if commits.present? merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project) merge_request.can_be_created = true - merge_request.compare_failed = false - - # Try to collect diff for merge request. - diffs = compare_result.diffs - - if diffs.present? - merge_request.compare_diffs = diffs - - elsif diffs == false - merge_request.can_be_created = false - merge_request.compare_failed = true - end + merge_request.compare = compare else merge_request.can_be_created = false - merge_request.compare_failed = false end commits = merge_request.compare_commits diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml index f125ecf7be5..3bc1b24b5e2 100644 --- a/app/views/abuse_reports/new.html.haml +++ b/app/views/abuse_reports/new.html.haml @@ -2,7 +2,7 @@ %h3.page-title Report abuse %p Please use this form to report users who create spam issues, comments or behave inappropriately. %hr -= form_for @abuse_report, html: { class: 'form-horizontal js-requires-input'} do |f| += form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f| = f.hidden_field :user_id - if @abuse_report.errors.any? .alert.alert-danger @@ -16,7 +16,7 @@ .form-group = f.label :message, class: 'control-label' .col-sm-10 - = f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true, value: sanitize(@ref_url) + = f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url) .help-block Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment. diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index 5c9403fa0c2..b748460a9f7 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -3,7 +3,7 @@ .js-broadcast-message-preview = render_broadcast_message(@broadcast_message.message.presence || "Your message here") -= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-requires-input'} do |f| += form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f| -if @broadcast_message.errors.any? .alert.alert-danger - @broadcast_message.errors.full_messages.each do |msg| @@ -11,7 +11,7 @@ .form-group = f.label :message, class: 'control-label' .col-sm-10 - = f.text_area :message, class: "form-control js-quick-submit js-autosize", + = f.text_area :message, class: "form-control js-autosize", required: true, data: { preview_path: preview_admin_broadcast_messages_path } .form-group.js-toggle-colors-container diff --git a/app/views/admin/users/keys.html.haml b/app/views/admin/users/keys.html.haml index 07110717082..0f644121e62 100644 --- a/app/views/admin/users/keys.html.haml +++ b/app/views/admin/users/keys.html.haml @@ -1,3 +1,3 @@ -- page_title "Keys", @user.name, "Users" +- page_title "SSH Keys", @user.name, "Users" = render 'admin/users/head' = render 'profiles/keys/key_table', admin: true diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 4bc761b3738..40f88261c10 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -14,7 +14,7 @@ .nav-controls = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| - = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field' + = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2" = render 'explore/projects/dropdown' - if current_user.can_create_project? = link_to new_project_path, class: 'btn btn-new' do diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml index 933a3edd0f0..0ebd7c01bab 100644 --- a/app/views/dashboard/projects/_projects.html.haml +++ b/app/views/dashboard/projects/_projects.html.haml @@ -1,6 +1 @@ -.projects-list-holder - - = render 'shared/projects/list', projects: @projects, ci: true - - :javascript - Dashboard.init() += render 'shared/projects/list', projects: @projects, ci: true diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index c248dbb695f..39e3e8e2738 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -1,41 +1,40 @@ -.pull-right.hidden-sm.hidden-xs - - if current_user - .dropdown.inline.append-right-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-globe - %span.light Visibility: - - if params[:visibility_level].present? - = visibility_level_label(params[:visibility_level].to_i) - - else +- if current_user + .dropdown + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + = icon('globe') + %span.light Visibility: + - if params[:visibility_level].present? + = visibility_level_label(params[:visibility_level].to_i) + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to explore_projects_filter_path(visibility_level: nil) do Any - %b.caret - %ul.dropdown-menu - %li - = link_to explore_projects_filter_path(visibility_level: nil) do - Any - - Gitlab::VisibilityLevel.values.each do |level| - %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } - = link_to explore_projects_filter_path(visibility_level: level) do - = visibility_level_icon(level) - = visibility_level_label(level) + - Gitlab::VisibilityLevel.values.each do |level| + %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } + = link_to explore_projects_filter_path(visibility_level: level) do + = visibility_level_icon(level) + = visibility_level_label(level) - - if @tags.present? - .dropdown.inline.append-right-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-tags - %span.light Tags: - - if params[:tag].present? - = params[:tag] - - else +- if @tags.present? + .dropdown + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + = icon('tags') + %span.light Tags: + - if params[:tag].present? + = params[:tag] + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to explore_projects_filter_path(tag: nil) do Any - %b.caret - %ul.dropdown-menu - %li - = link_to explore_projects_filter_path(tag: nil) do - Any - - @tags.each do |tag| - %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } - = link_to explore_projects_filter_path(tag: tag.name) do - %i.fa.fa-tag - = tag.name + - @tags.each do |tag| + %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } + = link_to explore_projects_filter_path(tag: tag.name) do + %i.fa.fa-tag + = tag.name diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml index 999a933390b..708fbc27f55 100644 --- a/app/views/explore/projects/_projects.html.haml +++ b/app/views/explore/projects/_projects.html.haml @@ -1,6 +1 @@ -- if projects.any? - .projects-list-holder - = render 'shared/projects/list', projects: projects -- else - .nothing-here-block - No such projects += render 'shared/projects/list', projects: projects diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index dca75498573..42b50481b9d 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -9,7 +9,7 @@ .top-area = render 'explore/projects/nav' -.gray-content-block.second-block.clearfix - = render 'filter' + .nav-controls + = render 'filter' = render 'projects', projects: @projects diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 209729dc7ee..794aa57b55a 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -8,5 +8,4 @@ = icon('plus') New Project -.projects-list-holder - = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true += render 'shared/projects/list', projects: @projects, stars: false, skip_namespace: true diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index a79a0fcdc8e..60234be8f83 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -1,5 +1,6 @@ - user = member.user - return unless user || member.invite? +- show_roles = local_assigns.fetch(:show_roles, true) %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %span{class: ("list-item-name" if show_controls)} @@ -28,7 +29,7 @@ = link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do Resend invite - - if should_user_see_group_roles?(current_user, @group) + - if show_roles && should_user_see_group_roles?(current_user, @group) %span.pull-right %strong.member-access-level= member.human_access - if show_controls diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 3894a0ece74..a8e1ed77da9 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -8,18 +8,18 @@ This will create milestone in every selected project %hr -= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f| += form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f| .row .col-md-6 .form-group = f.label :title, "Title", class: "control-label" .col-sm-10 - = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true + = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true .form-group.milestone-description = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' .clearfix .error-alert .form-group diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 20042e21bf2..54af2c3063c 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,6 +1,6 @@ .search = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| - = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false + = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1" = hidden_field_tag :group_id, @group.try(:id) - if @project && @project.persisted? = hidden_field_tag :project_id, @project.id diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml index 58af79716a7..879fc170f92 100644 --- a/app/views/profiles/_event_table.html.haml +++ b/app/views/profiles/_event_table.html.haml @@ -1,17 +1,15 @@ -.table-holder - %table.table#audits - %thead - %tr - %th Action - %th When +%h5.prepend-top-0 + History of authentications + +%ul.well-list + - events.each do |event| + %li + %span.description + = audit_icon(event.details[:with], class: "append-right-5") + Signed in with + = event.details[:with] + authentication + %span.pull-right + #{time_ago_in_words event.created_at} ago - %tbody - - events.each do |event| - %tr - %td - %span - Signed in with - %b= event.details[:with] - authentication - %td #{time_ago_in_words event.created_at} ago = paginate events, theme: "gitlab" diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index 8f45f41cfe3..f630c03e5f6 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,8 +1,11 @@ - page_title "Audit Log" - header_title page_title, audit_log_profile_path -.alert.alert-help.prepend-top-default - History of authentications - -.prepend-top-default -= render 'event_table', events: @events +.row.prepend-top-default + .col-lg-3.profile-settings-sidebar + %h3.prepend-top-0 + = page_title + %p + This is a security log of important events involving your account. + .col-lg-9 + = render 'event_table', events: @events diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 705e1804717..3f328f96cea 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,50 +1,49 @@ - page_title "Emails" - header_title page_title, profile_emails_path -.alert.alert-help.prepend-top-default - %ul - %li - Your - %b Primary Email - will be used for avatar detection and web based operations, such as edits and merges. - %li - Your - %b Notification Email - will be used for account notifications. - %li - Your - %b Public Email - will be displayed on your public profile. - %li - All email addresses will be used to identify your commits. - -.panel.panel-default - .panel-heading - Emails (#{@emails.count + 1}) - %ul.well-list#emails-table - %li - %strong= @primary - %span.label.label-success Primary Email - - if @primary === current_user.public_email - %span.label.label-info Public Email - - if @primary === current_user.notification_email - %span.label.label-info Notification Email - - @emails.each do |email| +.row.prepend-top-default + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + = page_title + %p + Control emails linked to your account + .col-lg-9 + %h4.prepend-top-0 + Add email address + = form_for 'email', url: profile_emails_path do |f| + .form-group + = f.label :email, class: 'label-light' + = f.text_field :email, class: 'form-control' + .prepend-top-default + = f.submit 'Add email address', class: 'btn btn-create' + %hr + %h4.prepend-top-0 + Linked emails (#{@emails.count + 1}) + .account-well.append-bottom-default + %ul + %li + Your Primary Email will be used for avatar detection and web based operations, such as edits and merges. + %li + Your Notification Email will be used for account notifications. + %li + Your Public Email will be displayed on your public profile. + %li + All email addresses will be used to identify your commits. + %ul.well-list %li - %strong= email.email - - if email.email === current_user.public_email - %span.label.label-info Public Email - - if email.email === current_user.notification_email - %span.label.label-info Notification Email - %span.cgray - added #{time_ago_with_tooltip(email.created_at)} - = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right' - -%h4 Add email address -= form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f| - .form-group - = f.label :email, class: 'control-label' - .col-sm-10 - = f.text_field :email, class: 'form-control' - .form-actions - = f.submit 'Add email address', class: 'btn btn-create' + = @primary + %span.pull-right + %span.label.label-success Primary Email + - if @primary === current_user.public_email + %span.label.label-info Public Email + - if @primary === current_user.notification_email + %span.label.label-info Notification Email + - @emails.each do |email| + %li + = email.email + %span.pull-right + - if email.email === current_user.public_email + %span.label.label-info Public Email + - if email.email === current_user.notification_email + %span.label.label-info Notification Email + = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right' diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index 2a8800de60e..4d78215ed3c 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -1,5 +1,5 @@ %div - = form_for [:profile, @key], html: { class: 'form-horizontal js-requires-input' } do |f| + = form_for [:profile, @key], html: { class: 'js-requires-input' } do |f| - if @key.errors.any? .alert.alert-danger %ul @@ -7,13 +7,11 @@ %li= msg .form-group - = f.label :key, class: 'control-label' - .col-sm-10 - = f.text_area :key, class: "form-control", rows: 8, autofocus: true, required: true + = f.label :key, class: 'label-light' + = f.text_area :key, class: "form-control", rows: 8, required: true .form-group - = f.label :title, class: 'control-label' - .col-sm-10= f.text_field :title, class: "form-control", required: true + = f.label :title, class: 'label-light' + = f.text_field :title, class: "form-control", required: true - .form-actions + .prepend-top-default = f.submit 'Add key', class: "btn btn-create" - = link_to "Cancel", profile_keys_path, class: "btn btn-cancel" diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index 9bbccbc45ea..25e9e8ff008 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -1,11 +1,14 @@ -%tr - %td - = link_to path_to_key(key, is_admin) do - %strong= key.title - %td - %code.key-fingerprint= key.fingerprint - %td - %span.cgray - added #{time_ago_with_tooltip(key.created_at)} - %td - = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right" +%li.key-list-item + .pull-left.append-right-10 + = icon 'key', class: "key-icon hidden-xs" + .key-list-item-info + = link_to path_to_key(key, is_admin), class: "title" do + = key.title + .description + = key.fingerprint + .pull-right + %span.key-created-at + created #{time_ago_with_tooltip(key.created_at)} ago + = link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do + %span.sr-only Remove + = icon('trash') diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml index 3bd1f1af162..dd7615400dc 100644 --- a/app/views/profiles/keys/_key_details.html.haml +++ b/app/views/profiles/keys/_key_details.html.haml @@ -1,5 +1,5 @@ - is_admin = defined?(admin) ? true : false -.row +.row.prepend-top-default .col-md-4 .panel.panel-default .panel-heading diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml index 8c9d546af4c..296cafa6e31 100644 --- a/app/views/profiles/keys/_key_table.html.haml +++ b/app/views/profiles/keys/_key_table.html.haml @@ -1,19 +1,11 @@ -- is_admin = defined?(admin) ? true : false +- is_admin = local_assigns.fetch(:admin, false) + - if @keys.any? - .table-holder - %table.table - %thead.panel-heading - %tr - %th Title - %th Fingerprint - %th Added at - %th - %tbody - - @keys.each do |key| - = render 'profiles/keys/key', key: key, is_admin: is_admin + %ul.well-list + = render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin } - else - .nothing-here-block + %p.profile-settings-message.text-center - if is_admin - User has no ssh keys + There are no SSH keys associated with this account. - else There are no SSH keys with access to your account. diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index c9a6a93f545..e0f8c9a5733 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,14 +1,21 @@ - page_title "SSH Keys" - header_title page_title, profile_keys_path -.top-area - .nav-text - Before you can add an SSH key you need to - = link_to "generate it.", help_page_path("ssh", "README") - .nav-controls - = link_to new_profile_key_path, class: "btn btn-new" do - = icon('plus') - Add SSH Key - -.prepend-top-default -= render 'key_table' +.row.prepend-top-default + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + = page_title + %p + SSH keys allow you to establish a secure connection between your computer and GitLab. + .col-lg-9 + %h5.prepend-top-0 + Add an SSH key + %p.profile-settings-content + Before you can add an SSH key you need to + = link_to "generate it.", help_page_path("ssh", "README") + = render 'form' + %hr + %h5 + Your SSH keys (#{@keys.count}) + %div.append-bottom-default + = render 'key_table' diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml deleted file mode 100644 index 13a18269d11..00000000000 --- a/app/views/profiles/keys/new.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -- page_title "Add SSH Keys" -%h3.page-title Add an SSH Key -%p.light - Paste your public key here. Read more about how to generate a key on #{link_to "the SSH help page", help_page_path("ssh", "README")}. -%hr -= render 'form' - -:javascript - $('#key_key').on('focusout', function(){ - var title = $('#key_title'), - val = $('#key_key').val(), - comment = val.match(/^\S+ \S+ (.+)\n?$/); - - if( comment && comment.length > 1 && title.val() == '' ){ - $('#key_title').val( comment[1] ).change(); - } - }); diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml index 742c5c4b68d..d0d044136f6 100644 --- a/app/views/profiles/notifications/_settings.html.haml +++ b/app/views/profiles/notifications/_settings.html.haml @@ -1,5 +1,5 @@ -%li - %span.notification.fa.fa-holder +%li.notification-list-item + %span.notification.fa.fa-holder.append-right-5 - if notification.global? = notification_icon(@notification) - else diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index d5f61d9f0ca..de80abd7f4d 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,8 +1,7 @@ - page_title "Notifications" - header_title page_title, profile_notifications_path -.prepend-top-default -= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f| += form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f| -if @user.errors.any? %div.alert.alert-danger %ul @@ -10,65 +9,66 @@ %li= msg = hidden_field_tag :notification_type, 'global' + .row + .col-lg-3.profile-settings-sidebar + %h4 + = page_title + %p + You can specify notification level per group or per project. + %p + By default, all projects and groups will use the global notifications setting. + .col-lg-9 + %h5 + Global notification settings + .form-group + = f.label :notification_email, class: "label-light" + = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" + .form-group + = f.label :notification_level, class: 'label-light' + .radio + = f.label :notification_level, value: Notification::N_DISABLED do + = f.radio_button :notification_level, Notification::N_DISABLED + .level-title + Disabled + %p You will not get any notifications via email - .form-group - = f.label :notification_email, class: "control-label" - .col-sm-10 - = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "form-control" + .radio + = f.label :notification_level, value: Notification::N_MENTION do + = f.radio_button :notification_level, Notification::N_MENTION + .level-title + On Mention + %p You will receive notifications only for comments in which you were @mentioned - .form-group - = f.label :notification_level, class: 'control-label' - .col-sm-10 - .radio - = f.label :notification_level, value: Notification::N_DISABLED do - = f.radio_button :notification_level, Notification::N_DISABLED - .level-title - Disabled - %p You will not get any notifications via email + .radio + = f.label :notification_level, value: Notification::N_PARTICIPATING do + = f.radio_button :notification_level, Notification::N_PARTICIPATING + .level-title + Participating + %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) - .radio - = f.label :notification_level, value: Notification::N_MENTION do - = f.radio_button :notification_level, Notification::N_MENTION - .level-title - On Mention - %p You will receive notifications only for comments in which you were @mentioned + .radio + = f.label :notification_level, value: Notification::N_WATCH do + = f.radio_button :notification_level, Notification::N_WATCH + .level-title + Watch + %p You will receive notifications for any activity - .radio - = f.label :notification_level, value: Notification::N_PARTICIPATING do - = f.radio_button :notification_level, Notification::N_PARTICIPATING - .level-title - Participating - %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) - - .radio - = f.label :notification_level, value: Notification::N_WATCH do - = f.radio_button :notification_level, Notification::N_WATCH - .level-title - Watch - %p You will receive notifications for any activity - - .gray-content-block - = f.submit 'Save changes', class: "btn btn-create" - -.row.all-notifications.prepend-top-default - .col-md-6 - %p - You can also specify notification level per group or per project. - %br - By default, all projects and groups will use the notification level set above. - %h4 Groups: - %ul.bordered-list - - @group_members.each do |group_member| - - notification = Notification.new(group_member) - = render 'settings', type: 'group', membership: group_member, notification: notification - - .col-md-6 - %p - 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: - %ul.bordered-list - - @project_members.each do |project_member| - - notification = Notification.new(project_member) - = render 'settings', type: 'project', membership: project_member, notification: notification + .prepend-top-default + = f.submit 'Update settings', class: "btn btn-create" + %hr + %h5 + Groups (#{@group_members.count}) + %div + %ul.bordered-list + - @group_members.each do |group_member| + - notification = Notification.new(group_member) + = render 'settings', type: 'group', membership: group_member, notification: notification + %h5 + Projects (#{@project_members.count}) + %p.account-well + To specify the notification level per project of a group you belong to, you need to be a member of the project itself, not only its group. + .append-bottom-default + %ul.bordered-list + - @project_members.each do |project_member| + - notification = Notification.new(project_member) + = render 'settings', type: 'project', membership: project_member, notification: notification diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index ab070c09beb..afd4f996b62 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,20 +1,18 @@ - page_title "Password" - header_title page_title, edit_profile_password_path -.alert.alert-help.prepend-top-default - - if @user.password_automatically_set? - Set your password. - - else - Change your password or recover your current one. - -.update-password.prepend-top-default - = form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f| - %div - %p.slead - - 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 the login page where you can log in with your new password. +.row.prepend-top-default + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + = page_title + %p + After a successful password update, you will be redirected to the login page where you can log in with your new password. + .col-lg-9 + %h5.prepend-top-0 + Change your password + - unless @user.password_automatically_set? + or recover your current one + = form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f| -if @user.errors.any? .alert.alert-danger %ul @@ -22,19 +20,16 @@ %li= msg - unless @user.password_automatically_set? .form-group - = f.label :current_password, class: 'control-label' - .col-sm-10 - = f.password_field :current_password, required: true, class: 'form-control' - %div - = link_to "Forgot your password?", reset_profile_password_path, method: :put - - .form-group - = f.label :password, 'New password', class: 'control-label' - .col-sm-10 + = f.label :current_password, class: 'label-light' + = f.password_field :current_password, required: true, class: 'form-control' + %p.help-block + You must provide your current password in order to change it. + .form-group + = f.label :password, 'New password', class: 'label-light' = f.password_field :password, required: true, class: 'form-control' - .form-group - = f.label :password_confirmation, class: 'control-label' - .col-sm-10 + .form-group + = f.label :password_confirmation, class: 'label-light' = f.password_field :password_confirmation, required: true, class: 'form-control' - .form-actions - = f.submit 'Save password', class: "btn btn-create" + .prepend-top-default.append-bottom-default + = f.submit 'Save password', class: "btn btn-create append-right-10" + = link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link" diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 1a53b4393e4..f80211669fb 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,56 +1,56 @@ - page_title 'Preferences' - header_title page_title, profile_preferences_path -.alert.alert-help.prepend-top-default - These settings allow you to customize the appearance and behavior of the site. - They are saved with your account and will persist to any device you use to - access the site. - -= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f| - .panel.panel-default.application-theme - .panel-heading += form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f| + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 Application theme - .panel-body - - Gitlab::Themes.each do |theme| - = label_tag do - .preview{class: theme.css_class} - = f.radio_button :theme_id, theme.id - = theme.name - - .panel.panel-default.syntax-theme - .panel-heading + %p + This setting allows you to customize the appearance of the site, ex. sidebar. + .col-lg-9.application-theme + - Gitlab::Themes.each do |theme| + = label_tag do + .preview{class: theme.css_class} + = f.radio_button :theme_id, theme.id + = theme.name + .col-sm-12 + %hr + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 Syntax highlighting theme - .panel-body - - Gitlab::ColorSchemes.each do |scheme| - = label_tag do - .preview= image_tag "#{scheme.css_class}-scheme-preview.png" - = f.radio_button :color_scheme_id, scheme.id - = scheme.name - - .panel.panel-default - .panel-heading + %p + This setting allow you to customize the appearance of the syntax. + .col-lg-9.syntax-theme + - Gitlab::ColorSchemes.each do |scheme| + = label_tag do + .preview= image_tag "#{scheme.css_class}-scheme-preview.png" + = f.radio_button :color_scheme_id, scheme.id + = scheme.name + .col-sm-12 + %hr + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 Behavior - .panel-body - .form-group - = f.label :layout, class: 'control-label' do - Layout width - .col-sm-10 - = f.select :layout, layout_choices, {}, class: 'form-control' - .help-block - Choose between fixed (max. 1200px) and fluid (100%) application layout. - .form-group - = f.label :dashboard, class: 'control-label' do - Default Dashboard - = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank') - .col-sm-10 - = f.select :dashboard, dashboard_choices, {}, class: 'form-control' - .form-group - = f.label :project_view, class: 'control-label' do - Project view - = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank') - .col-sm-10 - = f.select :project_view, project_view_choices, {}, class: 'form-control' - .help-block - Choose what content you want to see on a project's home page. - .panel-footer + %p + This setting allows you to customize the behavior of the system layout and default views. + .col-lg-9 + .form-group + = f.label :layout, class: 'label-light' do + Layout width + = f.select :layout, layout_choices, {}, class: 'form-control' + .help-block + Choose between fixed (max. 1200px) and fluid (100%) application layout. + .form-group + = f.label :dashboard, class: 'label-light' do + Default Dashboard + = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank') + = f.select :dashboard, dashboard_choices, {}, class: 'form-control' + .form-group + = f.label :project_view, class: 'label-light' do + Project view + = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank') + = f.select :project_view, project_view_choices, {}, class: 'form-control' + .help-block + Choose what content you want to see on a project's home page. + .form-group = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 64c4bdceff9..3d1ba49491c 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,107 +1,102 @@ -.alert.alert-help.prepend-top-default - This information will appear on your profile. - - if current_user.ldap_user? - Some options are unavailable for LDAP accounts - -.prepend-top-default -= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" }, authenticity_token: true do |f| += form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f| + = f.hidden_field :avatar_crop_x + = f.hidden_field :avatar_crop_y + = f.hidden_field :avatar_crop_size -if @user.errors.any? %div.alert.alert-danger %ul - @user.errors.full_messages.each do |msg| %li= msg .row - .col-md-7 + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + Public Avatar + %p + - if @user.avatar? + You can change your avatar here + - if Gitlab.config.gravatar.enabled + or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} + - else + You can upload an avatar here + - if Gitlab.config.gravatar.enabled + or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} + .col-lg-9 + .clearfix.avatar-image.append-bottom-default + = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' + %h5.prepend-top-0 + Upload new avatar + .prepend-top-5.append-bottom-10 + %a.btn.js-choose-user-avatar-button + Browse file... + %span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen + = f.file_field :avatar, class: "js-user-avatar-input hidden" + .help-block + The maximum file size allowed is 200KB. + - if @user.avatar? + %hr + = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-gray" + %hr + .row + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + Main settings + %p + This information will appear on your profile. + - if current_user.ldap_user? + Some options are unavailable for LDAP accounts + .col-lg-9 .form-group - = f.label :name, class: "control-label" - .col-sm-10 - = f.text_field :name, class: "form-control", required: true - %span.help-block Enter your name, so people you know can recognize you. + = f.label :name, class: "label-light" + = f.text_field :name, class: "form-control", required: true + %span.help-block Enter your name, so people you know can recognize you. .form-group - = f.label :email, class: "control-label" - .col-sm-10 - - if @user.ldap_user? && @user.ldap_email? - = f.text_field :email, class: "form-control", required: true, readonly: true - %span.help-block.light - Your email address was automatically set based on the LDAP server. + = f.label :email, class: "label-light" + - if @user.ldap_user? && @user.ldap_email? + = f.text_field :email, class: "form-control", required: true, readonly: true + %span.help-block.light + Your email address was automatically set based on the LDAP server. + - else + - if @user.temp_oauth_email? + = f.text_field :email, class: "form-control", required: true, value: nil - else - - if @user.temp_oauth_email? - = f.text_field :email, class: "form-control", required: true, value: nil - - else - = f.text_field :email, class: "form-control", required: true - - if @user.unconfirmed_email.present? - %span.help-block - Please click the link in the confirmation email before continuing. It was sent to - = succeed "." do - %strong #{@user.unconfirmed_email} - %p - = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post + = f.text_field :email, class: "form-control", required: true + - if @user.unconfirmed_email.present? + %span.help-block + Please click the link in the confirmation email before continuing. It was sent to + = succeed "." do + %strong #{@user.unconfirmed_email} + %p + = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post - - else - %span.help-block We also use email for avatar detection if no avatar is uploaded. + - else + %span.help-block We also use email for avatar detection if no avatar is uploaded. .form-group - = f.label :public_email, class: "control-label" - .col-sm-10 - = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2" - %span.help-block This email will be displayed on your public profile. + = f.label :public_email, class: "label-light" + = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2" + %span.help-block This email will be displayed on your public profile. .form-group - = f.label :skype, class: "control-label" - .col-sm-10= f.text_field :skype, class: "form-control" + = f.label :skype, class: "label-light" + = f.text_field :skype, class: "form-control" .form-group - = f.label :linkedin, class: "control-label" - .col-sm-10= f.text_field :linkedin, class: "form-control" + = f.label :linkedin, class: "label-light" + = f.text_field :linkedin, class: "form-control" .form-group - = f.label :twitter, class: "control-label" - .col-sm-10= f.text_field :twitter, class: "form-control" + = f.label :twitter, class: "label-light" + = f.text_field :twitter, class: "form-control" .form-group - = f.label :website_url, 'Website', class: "control-label" - .col-sm-10= f.text_field :website_url, class: "form-control" + = f.label :website_url, 'Website', class: "label-light" + = f.text_field :website_url, class: "form-control" .form-group - = f.label :location, 'Location', class: "control-label" - .col-sm-10= f.text_field :location, class: "form-control" + = f.label :location, 'Location', class: "label-light" + = f.text_field :location, class: "form-control" .form-group - = f.label :bio, class: "control-label" - .col-sm-10 - = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250 - %span.help-block Tell us about yourself in fewer than 250 characters. - - .col-md-5 - .light-well - = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' - - .clearfix - .profile-avatar-form-option - %p.light - - if @user.avatar? - You can change your avatar here - - if Gitlab.config.gravatar.enabled - %br - or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} - - else - You can upload an avatar here - - if Gitlab.config.gravatar.enabled - %br - or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} - %hr - %a.choose-btn.btn.btn-sm.js-choose-user-avatar-button - %i.fa.fa-paperclip - %span Choose File ... - - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-user-avatar-input hidden" - = f.hidden_field :avatar_crop_x - = f.hidden_field :avatar_crop_y - = f.hidden_field :avatar_crop_size - .light The maximum file size allowed is 200KB. - - if @user.avatar? - %hr - = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" - - - .form-actions - = f.submit 'Save changes', class: "btn btn-success" - = link_to "Cancel", user_path(current_user), class: "btn btn-cancel" + = f.label :bio, class: "label-light" + = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250 + %span.help-block Tell us about yourself in fewer than 250 characters. + .prepend-top-default.append-bottom-default + = f.submit 'Update profile settings', class: "btn btn-success" + = link_to "Cancel", user_path(current_user), class: "btn btn-cancel" .modal.modal-profile-crop .modal-dialog diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 10b02813733..f8b6fa253c4 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -10,7 +10,7 @@ %span.editor-file-name \/ = text_field_tag 'file_name', params[:file_name], placeholder: "File name", - required: true, class: 'form-control new-file-name js-quick-submit' + required: true, class: 'form-control new-file-name' .pull-right = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index 084608bbba3..84694203d7d 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -5,7 +5,7 @@ %a.close{href: "#", "data-dismiss" => "modal"} × %h3.page-title Create New Directory .modal-body - = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do + = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do .form-group = label_tag :dir_name, 'Directory name', class: 'control-label' .col-sm-10 diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index 1cf19a7d3db..2e1f32fd15e 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -6,7 +6,7 @@ %h3.page-title Delete #{@blob.name} .modal-body - = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-requires-input' do + = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-quick-submit js-requires-input' do = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}" .form-group diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 676924dc6ca..b1f50eb5f34 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -5,7 +5,7 @@ %a.close{href: "#", "data-dismiss" => "modal"} × %h3.page-title #{title} .modal-body - = form_tag form_path, method: method, class: 'js-upload-blob-form form-horizontal' do + = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do .dropzone .dropzone-previews.blob-upload-dropzone-previews %p.dz-message.light diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index a279e6eda55..effcce5a1c4 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -13,7 +13,7 @@ = icon('eye') = editing_preview_title(@blob.name) - = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do + = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 167fa615182..1dd2b5c0af7 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -5,7 +5,7 @@ New File .file-editor - = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do + = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-quick-submit js-requires-input') do = render 'projects/blob/editor', ref: @ref = render 'shared/new_commit_form', placeholder: "Add new file" diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index d668f483bcb..6086ad3661e 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -10,8 +10,8 @@ = parallel_diff_btn = render 'projects/diffs/stats', diff_files: diff_files -- if diff_files.count < diffs.size - = render 'projects/diffs/warning', diffs: diffs, shown_files_count: diff_files.count +- if diff_files.overflow? + = render 'projects/diffs/warning', diff_files: diff_files .files - diff_files.each_with_index do |diff_file, index| @@ -21,10 +21,3 @@ = render 'projects/diffs/file', i: index, project: project, diff_file: diff_file, diff_commit: diff_commit, blob: blob - -- if @diff_timeout - .alert.alert-danger - %h4 - Failed to collect changes - %p - Maybe diff is really big and operation failed with timeout. Try to get diff locally diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index d75e9ef2a49..9a8208202e4 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -6,7 +6,7 @@ %table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' } - last_line = 0 - - raw_diff_lines = diff_file.diff_lines + - raw_diff_lines = diff_file.diff_lines.to_a - diff_file.highlighted_diff_lines.each_with_index do |line, index| - type = line.type - last_line = line.new_pos diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index 63ede71e6f1..15536c17f8e 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -14,5 +14,5 @@ = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm" %p To preserve performance only - %strong #{shown_files_count} of #{diffs.size} + %strong #{diff_files.count} of #{diff_files.real_size} files are displayed. diff --git a/app/views/projects/forks/_projects.html.haml b/app/views/projects/forks/_projects.html.haml new file mode 100644 index 00000000000..2946e6dcbd0 --- /dev/null +++ b/app/views/projects/forks/_projects.html.haml @@ -0,0 +1,2 @@ += render 'shared/projects/list', projects: projects, use_creator_avatar: true, + forks: true, show_last_commit_as_description: true diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index ace22625d1d..4bcf2d9d533 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -4,8 +4,9 @@ == #{pluralize(@total_forks_count, 'fork')}: #{full_count_title} .nav-controls - = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short', - spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } + = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| + = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short', + spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } .dropdown %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'} @@ -38,18 +39,10 @@ Fork -.projects-list-holder - - if @forks.blank? - %ul.content-list - %li - .nothing-here-block No forks to show - - else - = render 'shared/projects/list', projects: @forks, use_creator_avatar: true, - forks: true, show_last_commit_as_description: true += render 'projects', projects: @forks - - if @private_forks_count > 0 - %ul.projects-list.private-forks-notice - %li.project-row - = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon') - %strong= pluralize(@private_forks_count, 'private fork') - %span you have no access to. +- if @private_forks_count > 0 + .private-forks-notice + = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon') + %strong= pluralize(@private_forks_count, 'private fork') + %span you have no access to. diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 6588d9bdbe1..33c48199ba5 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-quick-submit js-requires-input' } do |f| = render 'shared/issuable/form', f: f, issuable: @issue :javascript diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index d63d3a3ec20..be7a0bb5628 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| -if @label.errors.any? .row .col-sm-offset-2.col-sm-10 @@ -10,7 +10,7 @@ .form-group = f.label :title, class: 'control-label' .col-sm-10 - = f.text_field :title, class: "form-control js-quick-submit", required: true, autofocus: true + = f.text_field :title, class: "form-control", required: true, autofocus: true .form-group = f.label :description, class: 'control-label' .col-sm-10 diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 5b35acc66c0..f7ddd30c5a9 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -3,6 +3,10 @@ .pull-right %strong.append-right-20 + = link_to_label(label, type: :merge_request) do + = pluralize label.open_merge_requests_count, 'open merge request' + + %strong.append-right-20 = link_to_label(label) do = pluralize label.open_issues_count, 'open issue' diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 236a545c840..01dc7519bee 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -33,23 +33,18 @@ %div= msg - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? - - if @merge_request.compare_failed - .alert.alert-danger - %h4 Compare failed - %p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches. - - else - .light-well.append-bottom-default - .center - %h4 - There isn't anything to merge. - %p.slead - - if @merge_request.source_branch == @merge_request.target_branch - You'll need to use different branch names to get a valid comparison. - - else - %span.label-branch #{@merge_request.source_branch} - and - %span.label-branch #{@merge_request.target_branch} - are the same. + .light-well.append-bottom-default + .center + %h4 + There isn't anything to merge. + %p.slead + - if @merge_request.source_branch == @merge_request.target_branch + You'll need to use different branch names to get a valid comparison. + - else + %span.label-branch #{@merge_request.source_branch} + and + %span.label-branch #{@merge_request.target_branch} + are the same. .form-actions diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 4c5a9818e3e..9e59f7df71b 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -31,22 +31,18 @@ %li.diffs-tab.active = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do Changes - %span.badge= @diffs.size + %span.badge= @diffs.real_size .tab-content #commits.commits.tab-pane = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane.active - - if @diffs.present? - = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs - - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE .alert.alert-danger %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. %p To preserve performance the line changes are not shown. - else - .alert.alert-danger - %h4 This comparison includes a huge diff. - %p To preserve performance the line changes are not shown. + = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs - if @ci_commit #builds.builds.tab-pane = render "projects/merge_requests/show/builds" diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index d7bc26e24b9..7b5e2991c09 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -62,7 +62,7 @@ %li.diffs-tab = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do Changes - %span.badge= @merge_request.diffs.size + %span.badge= @merge_request.diff_size .tab-content #notes.notes.tab-pane.voting_notes diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 64cd484193e..1b0bae86ad4 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,5 +1,5 @@ - if @merge_request_diff.collected? - = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs, + = render "projects/diffs/diffs", diffs: @merge_request.diffs(diff_options), project: @merge_request.project, diff_refs: @merge_request.diff_refs - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index d9a1730a8bc..807833741af 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -1,6 +1,6 @@ - status_class = @ci_commit ? " ci-#{@ci_commit.status}" : nil -= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f| += form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-quick-submit js-requires-input' } do |f| = hidden_field_tag :authenticity_token, form_authenticity_token .accept-merge-holder.clearfix.js-toggle-container .clearfix diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 39aa2437e18..23f2bca7baf 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'} do |f| += form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f| -if @milestone.errors.any? .alert.alert-danger %ul @@ -9,12 +9,12 @@ .form-group = f.label :title, "Title", class: "control-label" .col-sm-10 - = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true + = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true .form-group.milestone-description = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' = render 'projects/notes/hints' .clearfix .error-alert diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index 5d78652befa..b5f076088c7 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -1,8 +1,8 @@ .note-edit-form - = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f| + = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, class: 'js-quick-submit' do |f| = note_target_fields(note) = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do - = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field js-quick-submit' + = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field' = render 'projects/notes/hints' .note-form-actions diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index f10a4145d62..09740d8ea12 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f| += form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f| = hidden_field_tag :view, diff_view = hidden_field_tag :line_type = note_target_fields(@note) @@ -8,7 +8,7 @@ = f.hidden_field :noteable_type = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-quick-submit' + = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text' = render 'projects/notes/hints' .error-alert diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index bc80f2f29ad..c4a3f06ee06 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -9,9 +9,9 @@ %strong #{@tag.name} .prepend-top-default - = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form' }) do |f| + = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form js-quick-submit' }) do |f| = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit form-control' + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' = render 'projects/notes/hints' .error-alert .form-actions.prepend-top-default diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 3a2f75fecaa..77c7c4d23de 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -10,7 +10,7 @@ New Tag %hr -= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-requires-input" do += form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-quick-submit js-requires-input" do .form-group = label_tag :tag_name, nil, class: 'control-label' .col-sm-10 @@ -30,7 +30,7 @@ = label_tag :release_description, 'Release notes', class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', attr: :release_description, classes: 'description js-quick-submit form-control' + = render 'projects/zen', attr: :release_description, classes: 'description form-control' = render 'projects/notes/hints' .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page. .form-actions diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 1d257818dcd..f0d1932e23c 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f| -if @page.errors.any? #error_explanation .alert.alert-danger @@ -15,7 +15,7 @@ = f.label :content, class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :content, classes: 'description form-control js-quick-submit' + = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' = render 'projects/notes/hints' .clearfix diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index eef7b7b5efd..60df348891c 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -18,7 +18,9 @@ = render 'shared/projects/list', projects: @objects - else = render partial: "search/results/#{@scope.singularize}", collection: @objects - = paginate @objects, theme: 'gitlab' + + - if @scope != 'projects' + = paginate @objects, theme: 'gitlab' :javascript $(".search-results .term").highlight("#{escape_javascript(params[:search])}"); diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index 6b77d24f50c..c9b7bd154af 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -30,7 +30,7 @@ .line-numbers - snippet_chunks.each do |chunk| - unless chunk[:data].empty? - - chunk[:data].lines.to_a.size.times do |index| + - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index| - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1 - i = index + offset = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index 7c57924277e..7afbaeddee8 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -7,7 +7,7 @@ .max-width-marker = text_area_tag 'commit_message', (params[:commit_message] || local_assigns[:text]), - class: 'form-control js-commit-message js-quick-submit', placeholder: local_assigns[:placeholder], + class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder], required: true, rows: (local_assigns[:rows] || 3), id: "commit_message-#{nonce}" - if local_assigns[:hint] diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index 089179e677a..bb5fff2d3bb 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,6 +1,6 @@ - if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? .no-ssh-key-message.alert.alert-warning.hidden-xs - You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path, class: 'alert-link'} to your profile + You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', profile_keys_path, class: 'alert-link'} to your profile .pull-right = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link' diff --git a/app/views/shared/groups/_list.html.haml b/app/views/shared/groups/_list.html.haml new file mode 100644 index 00000000000..1aa7ed1f2eb --- /dev/null +++ b/app/views/shared/groups/_list.html.haml @@ -0,0 +1,6 @@ +- if groups.any? + %ul.content-list + - groups.each_with_index do |group, i| + = render "shared/groups/group", group: group +- else + %h3 No groups found diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 90dc0062481..d5a4aad05d9 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -9,7 +9,7 @@ = f.label :title, class: 'control-label' .col-sm-10 = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off', - class: 'form-control pad js-gfm-input js-quick-submit', required: true + class: 'form-control pad js-gfm-input', required: true - if issuable.is_a?(MergeRequest) %p.help-block @@ -25,7 +25,7 @@ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, - classes: 'description form-control js-quick-submit' + classes: 'description form-control' = render 'projects/notes/hints' .clearfix .error-alert diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 2446e6c30ae..2e08bb2ac08 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -6,25 +6,19 @@ - ci = false unless local_assigns[:ci] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true +- remote = false unless local_assigns[:remote] == true -%ul.projects-list.content-list +.projects-list-holder - if projects.any? - - projects.each_with_index do |project, i| - - css_class = (i >= projects_limit) ? 'hide' : nil - = render "shared/projects/project", project: project, skip_namespace: skip_namespace, - avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, - forks: forks, show_last_commit_as_description: show_last_commit_as_description - - - if projects.size > projects_limit && projects.kind_of?(Array) - %li.bottom.center - .light - #{projects_limit} of #{pluralize(projects.count, 'project')} displayed. - = link_to '#', class: 'js-expand' do - Show all - = paginate projects, theme: "gitlab" if projects.respond_to? :total_pages + %ul.projects-list.content-list + - projects.each_with_index do |project, i| + - css_class = (i >= projects_limit) ? 'hide' : nil + = render "shared/projects/project", project: project, skip_namespace: skip_namespace, + avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, + forks: forks, show_last_commit_as_description: show_last_commit_as_description + = paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages - else .nothing-here-block No projects found :javascript - new ProjectsList(); - Dashboard.init(); + ProjectsList.init(); diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index d109635fa1e..bca816f22cb 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -8,117 +8,110 @@ = render 'shared/show_aside' -.cover-block - .cover-controls - - if @user == current_user - = link_to profile_path, class: 'btn btn-gray' do - = icon('pencil') - - elsif current_user - %span.report-abuse - - if @user.abuse_report - %button.btn.btn-danger{ title: 'Already reported for abuse', - data: { toggle: 'tooltip', placement: 'left', container: 'body' }} - = icon('exclamation-circle') - - else - = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray', - title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do - = icon('exclamation-circle') - - if current_user - - = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do - = icon('rss') - - .avatar-holder - = link_to avatar_icon(@user, 400), target: '_blank' do - = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' - .cover-title - = @user.name - - .cover-desc - %span.middle-dot-divider - @#{@user.username} - %span.middle-dot-divider - Member since #{@user.created_at.to_s(:medium)} - - - if @user.bio.present? +.user-profile + .cover-block + .cover-controls + - if @user == current_user + = link_to profile_path, class: 'btn btn-gray' do + = icon('pencil') + - elsif current_user + %span.report-abuse + - if @user.abuse_report + %button.btn.btn-danger{ title: 'Already reported for abuse', + data: { toggle: 'tooltip', placement: 'left', container: 'body' }} + = icon('exclamation-circle') + - else + = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray', + title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do + = icon('exclamation-circle') + - if current_user + + = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do + = icon('rss') + + .avatar-holder + = link_to avatar_icon(@user, 400), target: '_blank' do + = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' + .cover-title + = @user.name + + .cover-desc + %span.middle-dot-divider + @#{@user.username} + %span.middle-dot-divider + Member since #{@user.created_at.to_s(:medium)} + + - if @user.bio.present? + .cover-desc + %p.profile-user-bio + = @user.bio + .cover-desc - %p.profile-user-bio - = @user.bio - - .cover-desc - - unless @user.public_email.blank? - .profile-link-holder.middle-dot-divider - = link_to @user.public_email, "mailto:#{@user.public_email}" - - unless @user.skype.blank? - .profile-link-holder.middle-dot-divider - = link_to "skype:#{@user.skype}", title: "Skype" do - = icon('skype') - - unless @user.linkedin.blank? - .profile-link-holder.middle-dot-divider - = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do - = icon('linkedin-square') - - unless @user.twitter.blank? - .profile-link-holder.middle-dot-divider - = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do - = icon('twitter-square') - - unless @user.website_url.blank? - .profile-link-holder.middle-dot-divider - = link_to @user.short_website_url, @user.full_website_url - - unless @user.location.blank? - .profile-link-holder.middle-dot-divider - = icon('map-marker') - = @user.location - - %ul.nav-links.center - %li.active - = link_to "#activity", 'data-toggle' => 'tab' do - Activity - - if @groups.any? - %li - = link_to "#groups", 'data-toggle' => 'tab' do + - unless @user.public_email.blank? + .profile-link-holder.middle-dot-divider + = link_to @user.public_email, "mailto:#{@user.public_email}" + - unless @user.skype.blank? + .profile-link-holder.middle-dot-divider + = link_to "skype:#{@user.skype}", title: "Skype" do + = icon('skype') + - unless @user.linkedin.blank? + .profile-link-holder.middle-dot-divider + = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do + = icon('linkedin-square') + - unless @user.twitter.blank? + .profile-link-holder.middle-dot-divider + = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do + = icon('twitter-square') + - unless @user.website_url.blank? + .profile-link-holder.middle-dot-divider + = link_to @user.short_website_url, @user.full_website_url + - unless @user.location.blank? + .profile-link-holder.middle-dot-divider + = icon('map-marker') + = @user.location + + %ul.nav-links.center.user-profile-nav + %li.activity-tab + = link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do + Activity + %li.groups-tab + = link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do Groups - - if @contributed_projects.present? - %li - = link_to "#contributed", 'data-toggle' => 'tab' do + %li.contributed-tab + = link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do Contributed projects - - if @projects.present? - %li - = link_to "#personal", 'data-toggle' => 'tab' do + %li.projects-tab + = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do Personal projects -%div{ class: container_class } - .tab-content - .tab-pane.active#activity - .gray-content-block.white.second-block - %div{ class: container_class } - .user-calendar - %h4.center.light - %i.fa.fa-spinner.fa-spin - .user-calendar-activities + %div{ class: container_class } + .tab-content + #activity.tab-pane + .gray-content-block.white.second-block + %div{ class: container_class } + .user-calendar{data: {href: user_calendar_path}} + %h4.center.light + %i.fa.fa-spinner.fa-spin + .user-calendar-activities + .content_list{ data: {href: user_path} } + = spinner - .content_list - = spinner + #groups.tab-pane + - # This tab is always loaded via AJAX + + #contributed.contributed-projects.tab-pane + - # This tab is always loaded via AJAX - - if @groups.any? - .tab-pane#groups - %ul.content-list - - @groups.each do |group| - = render 'shared/groups/group', group: group - - - if @contributed_projects.present? - .tab-pane#contributed - .contributed-projects - = render 'shared/projects/list', - projects: @contributed_projects.sort_by(&:star_count).reverse, - projects_limit: 10, stars: true, avatar: true - - - if @projects.present? - .tab-pane#personal - .personal-projects - = render 'shared/projects/list', - projects: @projects.sort_by(&:star_count).reverse, - projects_limit: 10, stars: true, avatar: true + #projects.tab-pane + - # This tab is always loaded via AJAX + + .loading-status + = spinner :javascript - $(".user-calendar").load("#{user_calendar_path}"); + var userProfile; + + userProfile = new User({ + action: "#{controller.action_name}" + }); diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 2d44d8d4dc6..605ec4f04e5 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -141,7 +141,7 @@ class IrkerWorker end def files_count(commit) - files = "#{commit.diffs.count} file" + files = "#{commit.diffs.real_size} file" files += 's' if commit.diffs.count > 1 files end diff --git a/config/routes.rb b/config/routes.rb index a2acf170a6b..a918b5bd3f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -314,7 +314,7 @@ Rails.application.routes.draw do end end resource :preferences, only: [:show, :update] - resources :keys + resources :keys, except: [:new] resources :emails, only: [:index, :create, :destroy] resource :avatar, only: [:destroy] resource :two_factor_auth, only: [:new, :create, :destroy] do @@ -332,6 +332,15 @@ Rails.application.routes.draw do get 'u/:username/calendar_activities' => 'users#calendar_activities', as: :user_calendar_activities, constraints: { username: /.*/ } + get 'u/:username/groups' => 'users#groups', as: :user_groups, + constraints: { username: /.*/ } + + get 'u/:username/projects' => 'users#projects', as: :user_projects, + constraints: { username: /.*/ } + + get 'u/:username/contributed' => 'users#contributed', as: :user_contributed_projects, + constraints: { username: /.*/ } + get '/u/:username' => 'users#show', as: :user, constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } diff --git a/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb b/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb new file mode 100644 index 00000000000..f996ae74dca --- /dev/null +++ b/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb @@ -0,0 +1,5 @@ +class AddRealSizeToMergeRequestDiffs < ActiveRecord::Migration + def change + add_column :merge_request_diffs, :real_size, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 53a941d30de..71d9257a31e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -509,6 +509,7 @@ ActiveRecord::Schema.define(version: 20160222153918) do t.datetime "created_at" t.datetime "updated_at" t.string "base_commit_sha" + t.string "real_size" end add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 009532b50c1..5c527d55481 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -515,7 +515,7 @@ Parameters: } ``` -## Comments on merge requets +## Comments on merge requests Comments are done via the [notes](notes.md) resource. diff --git a/features/groups.feature b/features/groups.feature index 55fffb012ae..a60c3860b83 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -22,11 +22,23 @@ Feature: Groups When I visit group "Owned" issues page Then I should see issues from group "Owned" assigned to me + Scenario: I should not see issues from archived project in "Owned" group issues list + Given Group "Owned" has archived project + And the archived project have some issues + When I visit group "Owned" issues page + Then I should not see issues from the archived project + Scenario: I should see group "Owned" merge requests list Given project from group "Owned" has merge requests assigned to me When I visit group "Owned" merge requests page Then I should see merge requests from group "Owned" assigned to me + Scenario: I should not see merge requests from archived project in "Owned" group merge requests list + Given Group "Owned" has archived project + And the archived project have some merge_requests + When I visit group "Owned" merge requests page + Then I should not see merge requests from the archived project + Scenario: I should see edit group "Owned" page When I visit group "Owned" settings page And I change group "Owned" name to "new-name" diff --git a/features/profile/ssh_keys.feature b/features/profile/ssh_keys.feature index 581503fc5f9..b0d5b748916 100644 --- a/features/profile/ssh_keys.feature +++ b/features/profile/ssh_keys.feature @@ -9,7 +9,7 @@ Feature: Profile SSH Keys Then I should see my ssh keys Scenario: Add new ssh key - Given I click link "Add new" + Given I should see new ssh key form And I submit new ssh key "Laptop" Then I should see new ssh key "Laptop" diff --git a/features/project/badges/build.feature b/features/project/badges/build.feature index 9417f62d680..bcf80ed620e 100644 --- a/features/project/badges/build.feature +++ b/features/project/badges/build.feature @@ -20,3 +20,8 @@ Feature: Project Badges Build And project has another build that is running When I display builds badge for a master branch Then I should see a build running badge + + Scenario: I want to see a fresh badge on each request + Given recent build is successful + When I display builds badge for a master branch + Then I should see a badge that has not been cached diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 1e2a78a6029..7a6ae15ffa5 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -44,6 +44,18 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end end + step 'I should not see issues from the archived project' do + @archived_project.issues.each do |issue| + expect(page).not_to have_content issue.title + end + end + + step 'I should not see merge requests from the archived project' do + @archived_project.merge_requests.each do |mr| + expect(page).not_to have_content mr.title + end + end + step 'I should see merge requests from group "Owned" assigned to me' do assigned_to_me(:merge_requests).each do |issue| expect(page).to have_content issue.title[0..80] @@ -113,7 +125,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'Group "Owned" has archived project' do group = Group.find_by(name: 'Owned') - create(:project, namespace: group, archived: true, path: "archived-project") + @archived_project = create(:project, namespace: group, archived: true, path: "archived-project") end step 'I should see "archived" label' do @@ -124,6 +136,21 @@ class Spinach::Features::Groups < Spinach::FeatureSteps visit group_path(-1) end + step 'the archived project have some issues' do + create :issue, + project: @archived_project, + assignee: current_user, + author: current_user + end + + step 'the archived project have some merge requests' do + create :merge_request, + source_project: @archived_project, + target_project: @archived_project, + assignee: current_user, + author: current_user + end + private def assigned_to_me(key) diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 7895f643d0c..0c60328583a 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -13,7 +13,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps fill_in 'user_website_url', with: 'testurl' fill_in 'user_location', with: 'Ukraine' fill_in 'user_bio', with: 'I <3 GitLab' - click_button 'Save changes' + click_button 'Update profile settings' @user.reload end @@ -64,7 +64,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps page.within '.update-password' do fill_in "user_password", with: "22233344" fill_in "user_password_confirmation", with: "22233344" - click_button "Save" + click_button "Save password" end end @@ -73,7 +73,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps fill_in "user_current_password", with: "12345678" fill_in "user_password", with: "22233344" fill_in "user_password_confirmation", with: "22233344" - click_button "Save" + click_button "Save password" end end @@ -82,7 +82,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps fill_in "user_current_password", with: "12345678" fill_in "user_password", with: "password" fill_in "user_password_confirmation", with: "confirmation" - click_button "Save" + click_button "Save password" end end @@ -237,7 +237,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps page.find('#user_avatar_crop_y', visible: false).set('0') page.find('#user_avatar_crop_size', visible: false).set('256') - click_button "Save changes" + click_button "Update profile settings" @user.reload end diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb index c7f879d247d..a400488a532 100644 --- a/features/steps/profile/ssh_keys.rb +++ b/features/steps/profile/ssh_keys.rb @@ -7,8 +7,8 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps end end - step 'I click link "Add new"' do - click_link "Add SSH Key" + step 'I should see new ssh key form' do + expect(page).to have_content("Add an SSH key") end step 'I submit new ssh key "Laptop"' do diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb index cbfc35bed65..47540f356e9 100644 --- a/features/steps/project/badges/build.rb +++ b/features/steps/project/badges/build.rb @@ -20,6 +20,10 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps expect_badge('running') end + step 'I should see a badge that has not been cached' do + expect(page.response_headers).to include('Cache-Control' => 'no-cache') + end + def expect_badge(status) svg = Nokogiri::XML.parse(page.body) expect(page.response_headers).to include('Content-Type' => 'image/svg+xml') diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index f9fd7332464..93c37bf507f 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -126,8 +126,11 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I visit big commit page' do - stub_const('Commit::DIFF_SAFE_FILES', 20) - visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id) + # Create a temporary scope to ensure that the stub_const is removed after user + RSpec::Mocks.with_temporary_scope do + stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_lines: 1, max_files: 1 }) + visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id) + end end step 'I see big commit warning' do diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb index f0721094ee3..9856c510aa0 100644 --- a/features/steps/shared/user.rb +++ b/features/steps/shared/user.rb @@ -26,4 +26,20 @@ module SharedUser step 'I have no ssh keys' do @user.keys.delete_all end + + step 'I click on "Personal projects" tab' do + page.within '.nav-links' do + click_link 'Personal projects' + end + + expect(page).to have_css('.tab-content #projects.active') + end + + step 'I click on "Contributed projects" tab' do + page.within '.nav-links' do + click_link 'Contributed projects' + end + + expect(page).to have_css('.tab-content #contributed.active') + end end diff --git a/features/user.feature b/features/user.feature index 35eae842e77..e0cadba30a1 100644 --- a/features/user.feature +++ b/features/user.feature @@ -5,10 +5,12 @@ Feature: User # Signed out + @javascript Scenario: I visit user "John Doe" page while not signed in when he owns a public project Given "John Doe" owns internal project "Internal" And "John Doe" owns public project "Community" When I visit user "John Doe" page + And I click on "Personal projects" tab Then I should see user "John Doe" page And I should not see project "Enterprise" And I should not see project "Internal" @@ -16,28 +18,34 @@ Feature: User # Signed in as someone else + @javascript Scenario: I visit user "John Doe" page while signed in as someone else when he owns a public project Given "John Doe" owns public project "Community" And "John Doe" owns internal project "Internal" And I sign in as a user When I visit user "John Doe" page + And I click on "Personal projects" tab Then I should see user "John Doe" page And I should not see project "Enterprise" And I should see project "Internal" And I should see project "Community" + @javascript Scenario: I visit user "John Doe" page while signed in as someone else when he is not authorized to a public project Given "John Doe" owns internal project "Internal" And I sign in as a user When I visit user "John Doe" page + And I click on "Personal projects" tab Then I should see user "John Doe" page And I should not see project "Enterprise" And I should see project "Internal" And I should not see project "Community" + @javascript Scenario: I visit user "John Doe" page while signed in as someone else when he is not authorized to a project I can see Given I sign in as a user When I visit user "John Doe" page + And I click on "Personal projects" tab Then I should see user "John Doe" page And I should not see project "Enterprise" And I should not see project "Internal" @@ -45,19 +53,23 @@ Feature: User # Signed in as the user himself + @javascript Scenario: I visit user "John Doe" page while signed in as "John Doe" when he has a public project Given "John Doe" owns internal project "Internal" And "John Doe" owns public project "Community" And I sign in as "John Doe" When I visit user "John Doe" page + And I click on "Personal projects" tab Then I should see user "John Doe" page And I should see project "Enterprise" And I should see project "Internal" And I should see project "Community" + @javascript Scenario: I visit user "John Doe" page while signed in as "John Doe" when he has no public project Given I sign in as "John Doe" When I visit user "John Doe" page + And I click on "Personal projects" tab Then I should see user "John Doe" page And I should see project "Enterprise" And I should not see project "Internal" @@ -68,6 +80,7 @@ Feature: User Given I sign in as a user And "John Doe" has contributions When I visit user "John Doe" page + And I click on "Contributed projects" tab Then I should see user "John Doe" page And I should see contributed projects And I should see contributions calendar diff --git a/lib/api/commits.rb b/lib/api/commits.rb index f4efb651eb6..4544a41b1e3 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -48,7 +48,7 @@ module API sha = params[:sha] commit = user_project.commit(sha) not_found! "Commit" unless commit - commit.diffs + commit.diffs.to_a end # Get a commit's comments @@ -90,9 +90,9 @@ module API } if params[:path] && params[:line] && params[:line_type] - commit.diffs.each do |diff| + commit.diffs(all_diffs: true).each do |diff| next unless diff.new_path == params[:path] - lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) + lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line) lines.each do |line| next unless line.new_pos == params[:line].to_i && line.type == params[:line_type] diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a3b5f1eb8d3..b021db8fa5b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -181,7 +181,7 @@ module API class MergeRequestChanges < MergeRequest expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _| - compare.diffs + compare.diffs(all_diffs: true).to_a end end @@ -295,11 +295,11 @@ module API end expose :diffs, using: Entities::RepoDiff do |compare, options| - compare.diffs + compare.diffs(all_diffs: true).to_a end expose :compare_timeout do |compare, options| - compare.timeout + compare.diffs.overflow? end expose :same, as: :compare_same_ref diff --git a/lib/gitlab/compare_result.rb b/lib/gitlab/compare_result.rb deleted file mode 100644 index 0d696a1ee28..00000000000 --- a/lib/gitlab/compare_result.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Gitlab - class CompareResult - attr_reader :commits, :diffs - - def initialize(compare, diff_options = {}) - @commits, @diffs = compare.commits, compare.diffs(nil, diff_options) - end - end -end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index a484177ae33..faa2830c16e 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -21,7 +21,7 @@ module Gitlab # Array of Gitlab::DIff::Line objects def diff_lines - @lines ||= parser.parse(raw_diff.lines) + @lines ||= parser.parse(raw_diff.each_line).to_a end def highlighted_diff_lines diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb index 3666063bf8b..d0f6ba23ab4 100644 --- a/lib/gitlab/diff/parser.rb +++ b/lib/gitlab/diff/parser.rb @@ -5,52 +5,53 @@ module Gitlab def parse(lines) @lines = lines - lines_obj = [] line_obj_index = 0 line_old = 1 line_new = 1 type = nil - @lines.each do |line| - next if filename?(line) - - full_line = line.gsub(/\n/, '') - - if line.match(/^@@ -/) - type = "match" - - line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0 - line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0 - - next if line_old <= 1 && line_new <= 1 #top of file - lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) - line_obj_index += 1 - next - elsif line[0] == '\\' - type = 'nonewline' - lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) - line_obj_index += 1 - else - type = identification_type(line) - lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) - line_obj_index += 1 - end - - - case line[0] - when "+" - line_new += 1 - when "-" - line_old += 1 - when "\\" - # No increment - else - line_new += 1 - line_old += 1 + # By returning an Enumerator we make it possible to search for a single line (with #find) + # without having to instantiate all the others that come after it. + Enumerator.new do |yielder| + @lines.each do |line| + next if filename?(line) + + full_line = line.gsub(/\n/, '') + + if line.match(/^@@ -/) + type = "match" + + line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0 + line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0 + + next if line_old <= 1 && line_new <= 1 #top of file + yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + line_obj_index += 1 + next + elsif line[0] == '\\' + type = 'nonewline' + yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + line_obj_index += 1 + else + type = identification_type(line) + yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + line_obj_index += 1 + end + + + case line[0] + when "+" + line_new += 1 + when "-" + line_old += 1 + when "\\" + # No increment + else + line_new += 1 + line_old += 1 + end end end - - lines_obj end def empty? diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index a05ffeb9cd2..41f0edcaf7e 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -50,7 +50,7 @@ module Gitlab end def compare_timeout - compare.timeout if compare + diffs.overflow? if diffs end def reverse_compare? diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb index bbe400dad88..f09e4fcb154 100644 --- a/spec/controllers/commit_controller_spec.rb +++ b/spec/controllers/commit_controller_spec.rb @@ -81,7 +81,7 @@ describe Projects::CommitController do expect(response.body).to start_with("diff --git") # without whitespace option, there are more than 2 diff_splits - diff_splits = assigns(:diffs)[0].diff.split("\n") + diff_splits = assigns(:diffs).first.diff.split("\n") expect(diff_splits.length).to be <= 2 end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index be19f1abc53..788a609ee40 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::CompareController do to: ref_to) expect(response).to be_success - expect(assigns(:diffs).length).to be >= 1 + expect(assigns(:diffs).first).to_not be_nil expect(assigns(:commits).length).to be >= 1 end @@ -32,10 +32,10 @@ describe Projects::CompareController do w: 1) expect(response).to be_success - expect(assigns(:diffs).length).to be >= 1 + expect(assigns(:diffs).first).to_not be_nil expect(assigns(:commits).length).to be >= 1 # without whitespace option, there are more than 2 diff_splits - diff_splits = assigns(:diffs)[0].diff.split("\n") + diff_splits = assigns(:diffs).first.diff.split("\n") expect(diff_splits.length).to be <= 2 end @@ -48,7 +48,7 @@ describe Projects::CompareController do to: ref_to) expect(response).to be_success - expect(assigns(:diffs)).to eq([]) + expect(assigns(:diffs).to_a).to eq([]) expect(assigns(:commits)).to eq([]) end diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb new file mode 100644 index 00000000000..883bbaedd4e --- /dev/null +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe Projects::ForksController do + let(:user) { create(:user) } + let(:project) { create(:project, visibility_level: Project::PUBLIC) } + let(:forked_project) { Projects::ForkService.new(project, user).execute } + let(:group) { create(:group, owner: forked_project.creator) } + + describe 'GET index' do + def get_forks + get :index, + namespace_id: project.namespace.to_param, + project_id: project.to_param + end + + context 'when fork is public' do + before { forked_project.update_attribute(:visibility_level, Project::PUBLIC) } + + it 'should be visible for non logged in users' do + get_forks + + expect(assigns[:forks]).to be_present + end + end + + context 'when fork is private' do + before do + forked_project.update_attributes(visibility_level: Project::PRIVATE, group: group) + end + + it 'should not be visible for non logged in users' do + get_forks + + expect(assigns[:forks]).to be_blank + end + + context 'when user is logged in' do + before { sign_in(project.creator) } + + context 'when user is not a Project member neither a group member' do + it 'should not see the Project listed' do + get_forks + + expect(assigns[:forks]).to be_blank + end + end + + context 'when user is a member of the Project' do + before { forked_project.team << [project.creator, :developer] } + + it 'should see the project listed' do + get_forks + + expect(assigns[:forks]).to be_present + end + end + + context 'when user is a member of the Group' do + before { forked_project.group.add_developer(project.creator) } + + it 'should see the project listed' do + get_forks + + expect(assigns[:forks]).to be_present + end + end + + end + end + end + +end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 14986a74c2e..982c113e84b 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -22,65 +22,14 @@ describe DiffHelper do end end - describe 'allowed_diff_size' do + describe 'diff_options' do it 'should return hard limit for a diff if force diff is true' do allow(controller).to receive(:params) { { force_show_diff: true } } - expect(allowed_diff_size).to eq(1000) + expect(diff_options).to include(Commit.max_diff_options) end it 'should return safe limit for a diff if force diff is false' do - expect(allowed_diff_size).to eq(100) - end - end - - describe 'allowed_diff_lines' do - it 'should return hard limit for number of lines in a diff if force diff is true' do - allow(controller).to receive(:params) { { force_show_diff: true } } - expect(allowed_diff_lines).to eq(50000) - end - - it 'should return safe limit for numbers of lines a diff if force diff is false' do - expect(allowed_diff_lines).to eq(5000) - end - end - - describe 'safe_diff_files' do - it 'should return all files from a commit that is smaller than safe limits' do - expect(safe_diff_files(diffs, diff_refs).length).to eq(2) - end - - it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do - allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines - expect(safe_diff_files(diffs, diff_refs).length).to eq(1) - end - - it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do - allow(controller).to receive(:params) { { force_show_diff: true } } - allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines - expect(safe_diff_files(diffs, diff_refs).length).to eq(2) - end - - it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do - allow(controller).to receive(:params) { { force_show_diff: true } } - allow(diffs[1].diff).to receive(:lines).and_return([""] * 49999) #simulate 49999 lines - expect(safe_diff_files(diffs, diff_refs).length).to eq(1) - end - - it 'should return only a safe number of file diffs if a commit touches more files than the safe limits' do - large_diffs = diffs * 100 #simulate 200 diffs - expect(safe_diff_files(large_diffs, diff_refs).length).to eq(100) - end - - it 'should return all file diffs if a commit touches more files than the safe limits but force diff is true' do - allow(controller).to receive(:params) { { force_show_diff: true } } - large_diffs = diffs * 100 #simulate 200 diffs - expect(safe_diff_files(large_diffs, diff_refs).length).to eq(200) - end - - it 'should return a limited file diffs if a commit touches more files than the hard limits and force diff is true' do - allow(controller).to receive(:params) { { force_show_diff: true } } - very_large_diffs = diffs * 1000 #simulate 2000 diffs - expect(safe_diff_files(very_large_diffs, diff_refs).length).to eq(1000) + expect(diff_options).not_to include(:max_lines, :max_files) end end diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml index b80a28a33ea..e3788bee813 100644 --- a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml +++ b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml @@ -1,6 +1,6 @@ -%form{ action: '/foo' } - %input.js-quick-submit{ type: 'text' } - %textarea.js-quick-submit +%form.js-quick-submit{ action: '/foo' } + %input{ type: 'text' } + %textarea %input{ type: 'submit'} Submit %button.btn{ type: 'submit' } Submit diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index fe0dea77909..f576c39284e 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -47,7 +47,7 @@ eos end before do - @lines = parser.parse(diff.lines) + @lines = parser.parse(diff.lines).to_a end it { expect(@lines.size).to eq(30) } diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb index 56ae2a8d121..b2d7a799810 100644 --- a/spec/lib/gitlab/email/message/repository_push_spec.rb +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb @@ -72,7 +72,7 @@ describe Gitlab::Email::Message::RepositoryPush do describe '#compare_timeout' do subject { message.compare_timeout } - it { is_expected.to eq compare.timeout } + it { is_expected.to eq compare.diffs.overflow? } end describe '#reverse_compare?' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3ccb627a259..f9842d23afa 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -102,7 +102,7 @@ describe Project, models: true do expect(project2.errors[:limit_reached].first).to match(/Your project limit is 0/) end end - + describe 'project token' do it 'should set an random token if none provided' do project = FactoryGirl.create :empty_project, runners_token: '' @@ -524,30 +524,30 @@ describe Project, models: true do context 'for shared runners disabled' do let(:shared_runners_enabled) { false } - + it 'there are no runners available' do expect(project.any_runners?).to be_falsey end - + it 'there is a specific runner' do project.runners << specific_runner expect(project.any_runners?).to be_truthy end - + it 'there is a shared runner, but they are prohibited to use' do shared_runner expect(project.any_runners?).to be_falsey end - + it 'checks the presence of specific runner' do project.runners << specific_runner expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy end end - + context 'for shared runners enabled' do let(:shared_runners_enabled) { true } - + it 'there is a shared runner' do shared_runner expect(project.any_runners?).to be_truthy @@ -583,4 +583,67 @@ describe Project, models: true do end end + + describe '#rename_repo' do + let(:project) { create(:project) } + let(:gitlab_shell) { Gitlab::Shell.new } + + before do + # Project#gitlab_shell returns a new instance of Gitlab::Shell on every + # call. This makes testing a bit easier. + allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) + end + + it 'renames a repository' do + allow(project).to receive(:previous_changes).and_return('path' => ['foo']) + + ns = project.namespace_dir + + expect(gitlab_shell).to receive(:mv_repository). + ordered. + with("#{ns}/foo", "#{ns}/#{project.path}"). + and_return(true) + + expect(gitlab_shell).to receive(:mv_repository). + ordered. + with("#{ns}/foo.wiki", "#{ns}/#{project.path}.wiki"). + and_return(true) + + expect_any_instance_of(SystemHooksService). + to receive(:execute_hooks_for). + with(project, :rename) + + expect_any_instance_of(Gitlab::UploadsTransfer). + to receive(:rename_project). + with('foo', project.path, ns) + + expect(project).to receive(:expire_caches_before_rename) + + project.rename_repo + end + end + + describe '#expire_caches_before_rename' do + let(:project) { create(:project) } + let(:repo) { double(:repo, exists?: true) } + let(:wiki) { double(:wiki, exists?: true) } + + it 'expires the caches of the repository and wiki' do + allow(Repository).to receive(:new). + with('foo', project). + and_return(repo) + + allow(Repository).to receive(:new). + with('foo.wiki', project). + and_return(wiki) + + expect(repo).to receive(:expire_cache) + expect(repo).to receive(:expire_emptiness_caches) + + expect(wiki).to receive(:expire_cache) + expect(wiki).to receive(:expire_emptiness_caches) + + project.expire_caches_before_rename('foo') + end + end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index dfa18f69e05..1527eddfa48 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -137,7 +137,6 @@ end # keys GET /keys(.:format) keys#index # POST /keys(.:format) keys#create -# new_key GET /keys/new(.:format) keys#new # edit_key GET /keys/:id/edit(.:format) keys#edit # key GET /keys/:id(.:format) keys#show # PUT /keys/:id(.:format) keys#update @@ -151,10 +150,6 @@ describe Profiles::KeysController, "routing" do expect(post("/profile/keys")).to route_to('profiles/keys#create') end - it "to #new" do - expect(get("/profile/keys/new")).to route_to('profiles/keys#new') - end - it "to #edit" do expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1') end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 450250ba032..fea8182bd30 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -79,7 +79,7 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes.last.note).to include('changed to merged') } it { expect(@merge_request).to be_merged } - it { expect(@merge_request.diffs.length).to be > 0 } + it { expect(@merge_request.diffs.size).to be > 0 } it { expect(@fork_merge_request).to be_merged } it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 1bdc03af12d..5dcc39f5fdc 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -474,8 +474,8 @@ describe SystemNoteService, services: true do describe "existing reference" do before do - message = "[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]." - WebMock.stub_request(:get, jira_api_comment_url).to_return(body: "{\"comments\":[{\"body\":\"#{message}\"}]}") + message = %Q{[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\\n'#{commit.title}'} + WebMock.stub_request(:get, jira_api_comment_url).to_return(body: %Q({"comments":[{"body":"#{message}"}]})) end subject { described_class.cross_reference(jira_issue, commit, author) } |