diff --git a/CHANGELOG b/CHANGELOG index ce2fc5b194f410e060cd04dae9dcaf51b1ccf328..e317b1f6ce00e01f13fa802b631883e38c7fc228 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,13 +1,56 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Add two-factor recovery endpoint to internal API !5510 - Change merge_error column from string to text type + - Reduce contributions calendar data payload (ClemMakesApps) + - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) + - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) + - Add hover color to emoji icon (ClemMakesApps) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) + - Add `wiki_page_events` to project hook APIs (Ben Boeckel) + - Remove Gitorious import + - Add Sentry logging to API calls + - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Fix groups sort dropdown alignment (ClemMakesApps) - Added tests for diff notes - Add pipeline events to Slack integration !5525 v 8.11.1 (unreleased) + - Add delimiter to project stars and forks count (ClemMakesApps) + - Fix badge count alignment (ClemMakesApps) + - Fix branch title trailing space on hover (ClemMakesApps) + - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) + - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) + - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) + - Update merge_requests.md with a simpler way to check out a merge request. !5944 + - Fix button missing type (ClemMakesApps) + - Move to project dropdown with infinite scroll for better performance + - Load branches asynchronously in Cherry Pick and Revert dialogs. + - Add merge request versions !5467 + - Change using size to use count and caching it for number of group members. !5935 + - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) + - Reduce number of database queries on builds tab + - Capitalize mentioned issue timeline notes (ClemMakesApps) + - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) + - Adds response mime type to transaction metric action when it's not HTML + +v 8.11.3 (unreleased) + - Allow system info page to handle case where info is unavailable + - Label list shows all issues (opened or closed) with that label + - Don't show resolve conflicts link before MR status is updated + - Don't prevent viewing the MR when git refs for conflicts can't be found on disk + - Fix external issue tracker "Issues" link leading to 404s + +v 8.11.2 + - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 + - Use gitlab-workhorse 0.7.11 !5983 + - Does not halt the GitHub import process when an error occurs. !5763 - Fix file links on project page when default view is Files !5933 + - Fixed enter key in search input not working !5888 + +v 8.11.1 + - Pulled due to packaging error. v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 @@ -16,7 +59,6 @@ v 8.11.0 - Add Koding (online IDE) integration - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - - Add delimiter to project stars and forks count (ClemMakesApps) - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - Fix adding line comments on the initial commit to a repo !5900 - Fix the title of the toggle dropdown button. !5515 (herminiotorres) @@ -32,7 +74,6 @@ v 8.11.0 - Use long options for curl examples in documentation !5703 (winniehell) - Added tooltip listing label names to the labels value in the collapsed issuable sidebar - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - - Fix badge count alignment (ClemMakesApps) - GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - Allow naming U2F devices !5833 @@ -73,7 +114,6 @@ v 8.11.0 - Enforce 2FA restrictions on API authentication endpoints !5820 - Limit git rev-list output count to one in forced push check - Show deployment status on merge requests with external URLs - - Fix branch title trailing space on hover (ClemMakesApps) - Clean up unused routes (Josef Strzibny) - Fix issue on empty project to allow developers to only push to protected branches if given permission - API: Add enpoints for pipelines @@ -90,7 +130,6 @@ v 8.11.0 - Fix devise deprecation warnings. - Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 - Update version_sorter and use new interface for faster tag sorting - - Load branches asynchronously in Cherry Pick and Revert dialogs. - Optimize checking if a user has read access to a list of issues !5370 - Store all DB secrets in secrets.yml, under descriptive names !5274 - Fix syntax highlighting in file editor @@ -111,8 +150,6 @@ v 8.11.0 - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - Add pipeline events hook - - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) - Bump gitlab_git to speedup DiffCollection iterations - Rewrite description of a blocked user in admin settings. (Elias Werberich) - Make branches sortable without push permission !5462 (winniehell) @@ -124,14 +161,12 @@ v 8.11.0 - Fix search for notes which belongs to deleted objects - Allow Akismet to be trained by submitting issues as spam or ham !5538 - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) - Allow branch names ending with .json for graph and network page !5579 (winniehell) - Add the `sprockets-es6` gem - Improve OAuth2 client documentation (muteor) - Fix diff comments inverted toggle bug (ClemMakesApps) - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed - - Fix button missing type (ClemMakesApps) - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - Add commit stats in commit api. !5517 (dixpac) @@ -140,7 +175,6 @@ v 8.11.0 - edit_blob_link will use blob passed onto the options parameter - Make error pages responsive (Takuya Noguchi) - The performance of the project dropdown used for moving issues has been improved - - Move to project dropdown with infinite scroll for better performance - Fix skip_repo parameter being ignored when destroying a namespace - Add all builds into stage/job dropdowns on builds page - Change requests_profiles resource constraint to catch virtually any file diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 5b209ea2067d605a31380261d594aab1fc9fec3e..b4d6d12101febdd4c5792ced9aae7600069d928e 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.10 +0.7.11 diff --git a/Gemfile b/Gemfile index 68547b6fac8447f3c95ffa5e2385ab5774da1288..194379dd687b668529eea386ee1ed5d0980d26a0 100644 --- a/Gemfile +++ b/Gemfile @@ -349,5 +349,5 @@ gem 'paranoia', '~> 2.0' gem 'health_check', '~> 2.1.0' # System information -gem 'vmstat', '~> 2.1.1' +gem 'vmstat', '~> 2.2' gem 'sys-filesystem', '~> 1.1.6' diff --git a/Gemfile.lock b/Gemfile.lock index 5511d71893818bebc5cb1ae67c7c86d6ababba75..0c28975060cfea0762871c5d1b89ae14919dfb88 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -772,7 +772,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - vmstat (2.1.1) + vmstat (2.2.0) warden (1.2.6) rack (>= 1.0) web-console (2.3.0) @@ -980,7 +980,7 @@ DEPENDENCIES unicorn-worker-killer (~> 0.4.2) version_sorter (~> 2.1.0) virtus (~> 1.0.1) - vmstat (~> 2.1.1) + vmstat (~> 2.2) web-console (~> 2.0) webmock (~> 1.21.0) wikicloth (= 0.8.1) diff --git a/README.md b/README.md index fee93d5f9c304c61f6e1a863620cfc2e3ec86df5..3df8bfa04c748663267a71f1bf7f871f6211c789 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # GitLab [](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) +[](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [](https://codeclimate.com/github/gitlabhq/gitlabhq) ## Canonical source @@ -69,7 +70,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the GitLab is a Ruby on Rails application that runs on the following software: - Ubuntu/Debian/CentOS/RHEL -- Ruby (MRI) 2.1 +- Ruby (MRI) 2.3 - Git 2.7.4+ - Redis 2.8+ - MySQL or PostgreSQL diff --git a/app/assets/javascripts/abuse_reports.js.es6 b/app/assets/javascripts/abuse_reports.js.es6 index 748084b0307350b7979762a91cab7510609b1515..2fe46b9fd06164ee2eba8c6eed8c2869f7bb1b60 100644 --- a/app/assets/javascripts/abuse_reports.js.es6 +++ b/app/assets/javascripts/abuse_reports.js.es6 @@ -1,4 +1,3 @@ -window.gl = window.gl || {}; ((global) => { const MAX_MESSAGE_LENGTH = 500; const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; @@ -36,4 +35,4 @@ window.gl = window.gl || {}; } global.AbuseReports = AbuseReports; -})(window.gl); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index 1ab3c2197d82f60cfd44757db75e8dd340018b92..5ea6086ab779d5026eeccf662a304a06ebb530f9 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -26,7 +26,7 @@ event_filters = $.cookie("event_filter"); filter = sender.attr("id").split("_")[0]; $.cookie("event_filter", (event_filters !== filter ? filter : ""), { - path: '/' + path: gon.relative_url_root || '/' }); if (event_filters !== filter) { return sender.closest('li').toggleClass("active"); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index fc354dfd67766daa150ac0ad6daee27207f78d26..43a679501a77c0b6aa3af292a70067cece3b2320 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -288,7 +288,7 @@ new Aside(); if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') { $.cookie('pin_nav', 'false', { - path: '/', + path: gon.relative_url_root || '/', expires: 365 * 10 }); $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned'); @@ -313,7 +313,7 @@ $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded'); } $.cookie('pin_nav', doPinNav, { - path: '/', + path: gon.relative_url_root || '/', expires: 365 * 10 }); if ($.cookie('pin_nav') === 'true' || doPinNav) { diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index aee1c29eee3be894b6b76a652f336985f2f89fa9..ad12cb906e15bd9c0740840aa9cf9fa58ea094b5 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -320,6 +320,7 @@ frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); frequentlyUsedEmojis.push(emoji); return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { + path: gon.relative_url_root || '/', expires: 365 }); }; diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 1b7b63489ea3fc5eb300a90ed84ada71f3af53fb..8ac1ba7665e352fdeb4b3c8067a21aa81895b66a 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,10 +1,26 @@ -(function() { +(function(w) { $(function() { - return $("body").on("click", ".js-toggle-button", function(e) { - $(this).find('i').toggleClass('fa fa-chevron-down').toggleClass('fa fa-chevron-up'); - $(this).closest(".js-toggle-container").find(".js-toggle-content").toggle(); - return e.preventDefault(); + $('.js-toggle-button').on('click', function(e) { + e.preventDefault(); + $(this) + .find('.fa') + .toggleClass('fa-chevron-down fa-chevron-up') + .end() + .closest('.js-toggle-container') + .find('.js-toggle-content') + .toggle() + ; }); - }); -}).call(this); + // If we're accessing a permalink, ensure it is not inside a + // closed js-toggle-container! + var hash = w.gl.utils.getLocationHash(); + var anchor = hash && document.getElementById(hash); + var container = anchor && $(anchor).closest('.js-toggle-container'); + + if (container && container.find('.js-toggle-content').is(':hidden')) { + container.find('.js-toggle-button').trigger('click'); + anchor.scrollIntoView(); + } + }); +})(window); diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 10afa7e432985da0f7eeb3d721e77161ec98b3bd..d4d5927d3b03e7abfed017f1bdb941210f7f2d24 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -67,6 +67,14 @@ $.timeago.settings.strings = tmpLocale; }; + w.gl.utils.getDayDifference = function(a, b) { + var millisecondsPerDay = 1000 * 60 * 60 * 24; + var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()); + var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); + + return Math.floor((date2 - date1) / millisecondsPerDay); + } + })(window); }).call(this); diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index fffbfd19745d7887c2533f130ef3ecefb44a886e..533310cc87c22d88a863663d13698f1a64c1ef42 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -43,7 +43,7 @@ } return newUrl; }; - return w.gl.utils.removeParamQueryString = function(url, param) { + w.gl.utils.removeParamQueryString = function(url, param) { var urlVariables, variables; url = decodeURIComponent(url); urlVariables = url.split('&'); @@ -59,6 +59,16 @@ return results; })()).join('&'); }; + w.gl.utils.getLocationHash = function(url) { + var hashIndex; + if (typeof url === 'undefined') { + // Note: We can't use window.location.hash here because it's + // not consistent across browsers - Firefox will pre-decode it + url = window.location.href; + } + hashIndex = url.indexOf('#'); + return hashIndex === -1 ? null : url.substring(hashIndex + 1); + }; })(window); }).call(this); diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 4e1de4dfb7279864edd04818abcf1ab188242ba6..66e097c0a289d529c8cc47455c609bca573e5924 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -17,19 +17,15 @@ return $(this).parents('form').submit(); }); $('.hide-no-ssh-message').on('click', function(e) { - var path; - path = '/'; $.cookie('hide_no_ssh_message', 'false', { - path: path + path: gon.relative_url_root || '/' }); $(this).parents('.no-ssh-key-message').remove(); return e.preventDefault(); }); $('.hide-no-password-message').on('click', function(e) { - var path; - path = '/'; $.cookie('hide_no_password_message', 'false', { - path: path + path: gon.relative_url_root || '/' }); $(this).parents('.no-password-message').remove(); return e.preventDefault(); diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index dc4d51138261f1f71a90684367ac5b289a97c5e1..e3d5f413c77d373bedd0c53e1696421e722e723d 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -30,7 +30,7 @@ } if (!triggered) { return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), { - path: '/' + path: gon.relative_url_root || '/' }); } }); diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js index b46390ad8f43008ab8d2be72560fa1047c299806..6c4d88cf407b5f6fed01eacc96441f832e2e675a 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/user.js @@ -7,10 +7,8 @@ }); this.initTabs(); $('.hide-project-limit-message').on('click', function(e) { - var path; - path = '/'; $.cookie('hide_project_limit_message', 'false', { - path: path + path: gon.relative_url_root || '/' }); $(this).parents('.project-limit-message').remove(); return e.preventDefault(); diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 8b3dbf5f5ae1f096a9dde213887ed708a12f686d..74ecf4f4cf90112908c64c0ecb559001e5bf7e51 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -3,7 +3,6 @@ this.Calendar = (function() { function Calendar(timestamps, calendar_activities_path) { - var group, i; this.calendar_activities_path = calendar_activities_path; this.clickDay = bind(this.clickDay, this); this.currentSelectedDate = ''; @@ -13,26 +12,36 @@ this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; this.months = []; this.timestampsTmp = []; - i = 0; - group = 0; - _.each(timestamps, (function(_this) { - return function(count, date) { - var day, innerArray, newDate; - newDate = new Date(parseInt(date) * 1000); - day = newDate.getDay(); - if ((day === 0 && i !== 0) || i === 0) { - _this.timestampsTmp.push([]); - group++; - } - innerArray = _this.timestampsTmp[group - 1]; - innerArray.push({ - count: count, - date: newDate, - day: day - }); - return i++; - }; - })(this)); + var group = 0; + + var today = new Date() + today.setHours(0, 0, 0, 0, 0); + + var oneYearAgo = new Date(today); + oneYearAgo.setFullYear(today.getFullYear() - 1); + + var days = gl.utils.getDayDifference(oneYearAgo, today); + + for(var i = 0; i <= days; i++) { + var date = new Date(oneYearAgo); + date.setDate(date.getDate() + i); + + var day = date.getDay(); + var count = timestamps[date.getTime() * 0.001]; + + if ((day === 0 && i !== 0) || i === 0) { + this.timestampsTmp.push([]); + group++; + } + + var innerArray = this.timestampsTmp[group - 1]; + innerArray.push({ + count: count || 0, + date: date, + day: day + }); + } + this.colorKey = this.initColorKey(); this.color = this.initColor(); this.renderSvg(group); diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index c1e5305644ba5348549593893b0ac0397fe228f9..8984bce616c9803dcdd07c21cc546c758f71b433 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -248,7 +248,7 @@ li.note { img.emoji { height: 20px; - vertical-align: middle; + vertical-align: top; width: 20px; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 7d3a063d6c2c25cdaccce9334b8ce5ad5c15850b..be5c64c56d3c795f78d7472ed3743eeeb07ffa32 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -17,6 +17,12 @@ .dropdown { position: relative; + + .btn-link { + &:hover { + cursor: pointer; + } + } } .open { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index c1bb250b42d35b151a58729906fd9efe4ff7f96a..8c33e7d9a2e977978eeb14a730eb138b6ddf96e9 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -150,7 +150,7 @@ border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; max-height: 300px; - overflow: scroll; + overflow: auto; svg { position: relative; diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss index 84cc35239f94ceed3198faa50359d8b0282fa028..a4f76a9495a4ad4ad803fee9d001e8d3b1daa655 100644 --- a/app/assets/stylesheets/pages/import.scss +++ b/app/assets/stylesheets/pages/import.scss @@ -1,22 +1,3 @@ -i.icon-gitorious { - display: inline-block; - background-position: 0 0; - background-size: contain; - background-repeat: no-repeat; -} - -i.icon-gitorious-small { - background-image: image-url('gitorious-logo-blue.png'); - width: 13px; - height: 13px; -} - -i.icon-gitorious-big { - background-image: image-url('gitorious-logo-black.png'); - width: 18px; - height: 18px; -} - .import-jobs-from-col, .import-jobs-to-col { width: 40%; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index fcdaf671538a66e1fdafd5bd5c3e0a484b5c8e43..7fdd79fa8b902ef5c2da13685ea0466ae6dfac94 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -269,7 +269,7 @@ .builds { .table-holder { - overflow-x: scroll; + overflow-x: auto; } } @@ -375,6 +375,16 @@ } } +.mr-version-switch { + background: $background-color; + padding: $gl-btn-padding; + color: $gl-placeholder-color; + + a.btn-link { + color: $gl-dark-link-color; + } +} + .merge-request-details { .title { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 08d1692c888c5110268116aff95c6cf890140fb0..54124a3d65828cc18afb338f8ded90815849b5ab 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -281,19 +281,13 @@ ul.notes { font-size: 17px; } - &.js-note-delete { - i { - &:hover { - color: $gl-text-red; - } + &:hover { + .danger-highlight { + color: $gl-text-red; } - } - &.js-note-edit { - i { - &:hover { - color: $gl-link-color; - } + .link-highlight { + color: $gl-link-color; } } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 6fa097e3bf165a1b877fe7978a4fa97c2e489abf..9bdf0d250bb639da72ac59781bf67167403dd4ca 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -254,7 +254,6 @@ width: 100%; overflow: auto; white-space: nowrap; - max-height: 500px; transition: max-height 0.3s, padding 0.3s; &.graph-collapsed { @@ -265,7 +264,6 @@ .pipeline-visualization { position: relative; - min-width: 1220px; ul { padding: 0; diff --git a/app/controllers/admin/system_info_controller.rb b/app/controllers/admin/system_info_controller.rb index e4c730088269169b3b5b3953d77e1e540b6776f7..ca04a17caa14b8b395b53ed1201beda33c1e79ae 100644 --- a/app/controllers/admin/system_info_controller.rb +++ b/app/controllers/admin/system_info_controller.rb @@ -29,7 +29,8 @@ class Admin::SystemInfoController < Admin::ApplicationController ] def show - system_info = Vmstat.snapshot + @cpus = Vmstat.cpu rescue nil + @memory = Vmstat.memory rescue nil mounts = Sys::Filesystem.mounts @disks = [] @@ -50,10 +51,5 @@ class Admin::SystemInfoController < Admin::ApplicationController rescue Sys::Filesystem::Error end end - - @cpus = system_info.cpus.length - - @mem_used = system_info.memory.active_bytes - @mem_total = system_info.memory.total_bytes end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 634d36a44671ed450597061fcbb3f6bd9f9903fa..ebc2a4651ba5959b3d351b1f06d63316cc2bd643 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base include Gitlab::GonHelper include GitlabRoutingHelper include PageLayoutHelper + include SentryHelper include WorkhorseHelper before_action :authenticate_user_from_private_token! @@ -24,7 +25,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :abilities, :can?, :current_application_settings - helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled? + helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -46,28 +47,6 @@ class ApplicationController < ActionController::Base protected - def sentry_context - if Rails.env.production? && current_application_settings.sentry_enabled - if current_user - Raven.user_context( - id: current_user.id, - email: current_user.email, - username: current_user.username, - ) - end - - Raven.tags_context(program: sentry_program_context) - end - end - - def sentry_program_context - if Sidekiq.server? - 'sidekiq' - else - 'rails' - end - end - # This filter handles both private tokens and personal access tokens def authenticate_user_from_private_token! token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence @@ -271,10 +250,6 @@ class ApplicationController < ActionController::Base Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present? end - def gitorious_import_enabled? - current_application_settings.import_sources.include?('gitorious') - end - def google_code_import_enabled? current_application_settings.import_sources.include?('google_code') end diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb deleted file mode 100644 index a4c4ad230279d342b8e8ef43b135a4552b95c0a2..0000000000000000000000000000000000000000 --- a/app/controllers/import/gitorious_controller.rb +++ /dev/null @@ -1,47 +0,0 @@ -class Import::GitoriousController < Import::BaseController - before_action :verify_gitorious_import_enabled - - def new - redirect_to client.authorize_url(callback_import_gitorious_url) - end - - def callback - session[:gitorious_repos] = params[:repos] - redirect_to status_import_gitorious_path - end - - def status - @repos = client.repos - - @already_added_projects = current_user.created_projects.where(import_type: "gitorious") - already_added_projects_names = @already_added_projects.pluck(:import_source) - - @repos.reject! { |repo| already_added_projects_names.include? repo.full_name } - end - - def jobs - jobs = current_user.created_projects.where(import_type: "gitorious").to_json(only: [:id, :import_status]) - render json: jobs - end - - def create - @repo_id = params[:repo_id] - repo = client.repo(@repo_id) - @target_namespace = params[:new_namespace].presence || repo.namespace - @project_name = repo.name - - namespace = get_or_create_namespace || (render and return) - - @project = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, current_user).execute - end - - private - - def client - @client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos]) - end - - def verify_gitorious_import_enabled - render_404 unless gitorious_import_enabled? - end -end diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index 5962f74c39bfa431debf2732d265efd00fee2e4d..ada7db3c552bf6c18dbd53b9e94cd8452abf5ecc 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -4,7 +4,7 @@ class Projects::AvatarsController < Projects::ApplicationController before_action :authorize_admin_project!, only: [:destroy] def show - @blob = @repository.blob_at_branch('master', @project.avatar_in_git) + @blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git) if @blob headers['X-Content-Type-Options'] = 'nosniff' diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 7b0189150f801c64d148a620a41f885202170af7..7c03dcd2e64cc36f685b09143ac1acc8877a8f8a 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -212,7 +212,7 @@ class Projects::IssuesController < Projects::ApplicationController if action_name == 'new' redirect_to external.new_issue_path else - redirect_to external.issues_url + redirect_to external.project_path end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 6a8c7166b395ba724ca3b568e61ff4f7a71a911c..4f5f3b6aa09d5a650ec3fb55fe4c2d826260b549 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -83,12 +83,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diffs apply_diff_view_cookie! - @merge_request_diff = @merge_request.merge_request_diff + @merge_request_diff = + if params[:diff_id] + @merge_request.merge_request_diffs.find(params[:diff_id]) + else + @merge_request.merge_request_diff + end respond_to do |format| format.html { define_discussion_vars } format.json do - @diffs = @merge_request.diffs(diff_options) + unless @merge_request_diff.latest? + # Disable comments if browsing older version of the diff + @diff_notes_disabled = true + end + + @diffs = @merge_request_diff.diffs(diff_options) render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 2a6385c1029514ef981ec8cdbb5776b0ecec6da9..fc52cd2f367f2e629d97985882799c3f9b748d4a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] before_action :assign_ref_vars, only: [:show], if: :repo_exists? - before_action :assign_tree_vars, only: [:show], if: [:repo_exists?, :project_view_files?] + before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?] # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] @@ -332,11 +332,4 @@ class ProjectsController < Projects::ApplicationController def get_id project.repository.root_ref end - - # ExtractsPath will set @id = project.path on the show route, but it has to be the - # branch name for the tree view to work correctly. - def assign_tree_vars - @id = get_id - tree - end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 249d18c4486975ef40a63283a818757397384623..356f27f2d5dad79bbc5011261f4afb0431223a6b 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -116,6 +116,17 @@ module ProjectsHelper license.nickname || license.name end + def last_push_event + return unless current_user + + project_ids = [@project.id] + if fork = current_user.fork_of(@project) + project_ids << fork.id + end + + current_user.recent_push(project_ids) + end + private def get_project_nav_tabs(project, current_user) @@ -351,16 +362,6 @@ module ProjectsHelper namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE') end - def last_push_event - return unless current_user - - if fork = current_user.fork_of(@project) - current_user.recent_push(fork.id) - else - current_user.recent_push(@project.id) - end - end - def readme_cache_key sha = @project.commit.try(:sha) || 'nil' [@project.path_with_namespace, sha, "readme"].join('-') diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..f8cccade15b5520ffb8405d393fab7fbc34f0ea9 --- /dev/null +++ b/app/helpers/sentry_helper.rb @@ -0,0 +1,27 @@ +module SentryHelper + def sentry_enabled? + Rails.env.production? && current_application_settings.sentry_enabled? + end + + def sentry_context + return unless sentry_enabled? + + if current_user + Raven.user_context( + id: current_user.id, + email: current_user.email, + username: current_user.username, + ) + end + + Raven.tags_context(program: sentry_program_context) + end + + def sentry_program_context + if Sidekiq.server? + 'sidekiq' + else + 'rails' + end + end +end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index f0bcb2d7cdac7ffa6110450750c73a0378d9392f..246477ffe88e05b2952e21bb610cbef01f55564a 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -146,7 +146,7 @@ class ApplicationSetting < ActiveRecord::Base default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project], + import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], require_two_factor_authentication: false, diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 096b3b801af1f5fbec964f70438e32a3a8fe00a5..23c8de6f6503e72248655d87c89567e9a50a1cc3 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -352,7 +352,7 @@ module Ci end def artifacts? - !artifacts_expired? && artifacts_file.exists? + !artifacts_expired? && self[:artifacts_file].present? end def artifacts_metadata? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 087abe4cbb1b9f03e27d56a5e41338471b7aee9d..03812cd195f8f2adab3bbee66986c875f7df91e4 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -1,7 +1,7 @@ module Ci class Pipeline < ActiveRecord::Base extend Ci::Model - include Statuseable + include HasStatus self.table_name = 'ci_commits' @@ -83,7 +83,7 @@ module Ci end def stages_with_latest_statuses - statuses.latest.order(:stage_idx).group_by(&:stage) + statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage) end def project_id diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 84ceeac7d3eb29dd005e57514107a834a96b89e8..4a6289244997c61c220fb36513cd2cc722722d46 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,5 +1,5 @@ class CommitStatus < ActiveRecord::Base - include Statuseable + include HasStatus include Importable self.table_name = 'ci_builds' @@ -25,6 +25,8 @@ class CommitStatus < ActiveRecord::Base scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } + scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) } + scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) } state_machine :status do event :enqueue do diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/has_status.rb similarity index 99% rename from app/models/concerns/statuseable.rb rename to app/models/concerns/has_status.rb index 750f937b72407bbdd9e827c41093c4edc86b5bd9..f7b8352405c6ea665697a6467b1d92f1e7792ad2 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/has_status.rb @@ -1,4 +1,4 @@ -module Statuseable +module HasStatus extend ActiveSupport::Concern AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5330a07ee35ffd97b8a472cf79ad463b9bd19323..1d05e4a85d1bbb2baf8ef2d7e353826368d43c99 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -10,14 +10,16 @@ class MergeRequest < ActiveRecord::Base belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" belongs_to :merge_user, class_name: "User" - has_one :merge_request_diff, dependent: :destroy + has_many :merge_request_diffs, dependent: :destroy + has_one :merge_request_diff, + -> { order('merge_request_diffs.id DESC') } has_many :events, as: :target, dependent: :destroy serialize :merge_params, Hash - after_create :create_merge_request_diff, unless: :importing? - after_update :update_merge_request_diff + after_create :ensure_merge_request_diff, unless: :importing? + after_update :reload_diff_if_branch_changed delegate :commits, :real_size, to: :merge_request_diff, prefix: nil @@ -170,10 +172,10 @@ class MergeRequest < ActiveRecord::Base end def diffs(diff_options = nil) - if self.compare - self.compare.diffs(diff_options) + if compare + compare.diffs(diff_options) else - Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options) + merge_request_diff.diffs(diff_options) end end @@ -184,8 +186,8 @@ class MergeRequest < ActiveRecord::Base def diff_base_commit if persisted? merge_request_diff.base_commit - elsif diff_start_commit && diff_head_commit - self.target_project.merge_base_commit(diff_start_sha, diff_head_sha) + else + branch_merge_base_commit end end @@ -246,6 +248,15 @@ class MergeRequest < ActiveRecord::Base target_project.repository.commit(target_branch) if target_branch_ref end + def branch_merge_base_commit + start_sha = target_branch_sha + head_sha = source_branch_sha + + if start_sha && head_sha + target_project.merge_base_commit(start_sha, head_sha) + end + end + def target_branch_sha @target_branch_sha || target_branch_head.try(:sha) end @@ -267,16 +278,16 @@ class MergeRequest < ActiveRecord::Base # Return diff_refs instance trying to not touch the git repository def diff_sha_refs if merge_request_diff && merge_request_diff.diff_refs_by_sha? - return Gitlab::Diff::DiffRefs.new( - base_sha: merge_request_diff.base_commit_sha, - start_sha: merge_request_diff.start_commit_sha, - head_sha: merge_request_diff.head_commit_sha - ) + merge_request_diff.diff_refs else diff_refs end end + def branch_merge_base_sha + branch_merge_base_commit.try(:sha) + end + def validate_branches if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" @@ -309,21 +320,31 @@ class MergeRequest < ActiveRecord::Base end end - def update_merge_request_diff + def ensure_merge_request_diff + merge_request_diff || create_merge_request_diff + end + + def create_merge_request_diff + merge_request_diffs.create + reload_merge_request_diff + end + + def reload_merge_request_diff + merge_request_diff(true) + end + + def reload_diff_if_branch_changed if source_branch_changed? || target_branch_changed? reload_diff end end def reload_diff - return unless merge_request_diff && open? + return unless open? old_diff_refs = self.diff_refs - - merge_request_diff.reload_content - + create_merge_request_diff MergeRequests::MergeRequestDiffCacheService.new.execute(self) - new_diff_refs = self.diff_refs update_diff_notes_positions( @@ -777,8 +798,12 @@ class MergeRequest < ActiveRecord::Base return @conflicts_can_be_resolved_in_ui = false unless has_complete_diff_refs? begin - @conflicts_can_be_resolved_in_ui = conflicts.files.each(&:lines) - rescue Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing + # Try to parse each conflict. If the MR's mergeable status hasn't been updated, + # ensure that we don't say there are conflicts to resolve when there are no conflict + # files. + conflicts.files.each(&:lines) + @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0 + rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing @conflicts_can_be_resolved_in_ui = false end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 32cc6a3bfead9693385210c04a5b11bc3fbe5f53..445179a4487b47016525e83b3ed156cc23c09544 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -8,8 +8,6 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request - delegate :source_branch_sha, :target_branch_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil - state_machine :state, initial: :empty do state :collected state :overflow @@ -24,12 +22,47 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_commits serialize :st_diffs - after_create :reload_content, unless: :importing? - after_save :keep_around_commits, unless: :importing? + # All diff information is collected from repository after object is created. + # It allows you to override variables like head_commit_sha before getting diff. + after_create :save_git_content, unless: :importing? + + def self.select_without_diff + select(column_names - ['st_diffs']) + end - def reload_content + # Collect information about commits and diff from repository + # and save it to the database as serialized data + def save_git_content + ensure_commits_sha + save_commits reload_commits - reload_diffs + save_diffs + keep_around_commits + end + + def ensure_commits_sha + merge_request.fetch_ref + self.start_commit_sha ||= merge_request.target_branch_sha + self.head_commit_sha ||= merge_request.source_branch_sha + self.base_commit_sha ||= find_base_sha + save + end + + # Override head_commit_sha to keep compatibility with merge request diff + # created before version 8.4 that does not store head_commit_sha in separate db field. + def head_commit_sha + if persisted? && super.nil? + last_commit.try(:sha) + else + super + end + end + + # This method will rely on repository branch sha + # in case start_commit_sha is nil. Its necesarry for old merge request diff + # created before version 8.4 to work + def safe_start_commit_sha + start_commit_sha || merge_request.target_branch_sha end def size @@ -38,14 +71,11 @@ class MergeRequestDiff < ActiveRecord::Base def raw_diffs(options = {}) if options[:ignore_whitespace_change] - @raw_diffs_no_whitespace ||= begin - compare = Gitlab::Git::Compare.new( + @diffs_no_whitespace ||= + Gitlab::Git::Compare.new( repository.raw_repository, - self.start_commit_sha || self.target_branch_sha, - self.head_commit_sha || self.source_branch_sha, - ) - compare.diffs(options) - end + safe_start_commit_sha, + head_commit_sha).diffs(options) else @raw_diffs ||= {} @raw_diffs[options] ||= load_diffs(st_diffs, options) @@ -56,6 +86,11 @@ class MergeRequestDiff < ActiveRecord::Base @commits ||= load_commits(st_commits || []) end + def reload_commits + @commits = nil + commits + end + def last_commit commits.first end @@ -65,55 +100,60 @@ class MergeRequestDiff < ActiveRecord::Base end def base_commit - return unless self.base_commit_sha + return unless base_commit_sha - project.commit(self.base_commit_sha) + project.commit(base_commit_sha) end def start_commit - return unless self.start_commit_sha + return unless start_commit_sha - project.commit(self.start_commit_sha) + project.commit(start_commit_sha) end def head_commit - return last_commit unless self.head_commit_sha + return unless head_commit_sha + + project.commit(head_commit_sha) + end + + def diff_refs + return unless start_commit_sha || base_commit_sha - project.commit(self.head_commit_sha) + Gitlab::Diff::DiffRefs.new( + base_sha: base_commit_sha, + start_sha: start_commit_sha, + head_sha: head_commit_sha + ) end def diff_refs_by_sha? base_commit_sha? && head_commit_sha? && start_commit_sha? end + def diffs(diff_options = nil) + Gitlab::Diff::FileCollection::MergeRequestDiff.new(self, diff_options: diff_options) + end + + def project + merge_request.target_project + end + def compare @compare ||= - begin - # Update ref for merge request - merge_request.fetch_ref + Gitlab::Git::Compare.new( + repository.raw_repository, + safe_start_commit_sha, + head_commit_sha + ) + end - Gitlab::Git::Compare.new( - repository.raw_repository, - self.target_branch_sha, - self.source_branch_sha - ) - end + def latest? + self == merge_request.merge_request_diff end private - # Collect array of Git::Commit objects - # between target and source branches - def unmerged_commits - commits = compare.commits - - if commits.present? - commits = Commit.decorate(commits, merge_request.source_project).reverse - end - - commits - end - def dump_commits(commits) commits.map(&:to_hash) end @@ -122,26 +162,21 @@ class MergeRequestDiff < ActiveRecord::Base array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } end - # Reload all commits related to current merge request from repo + # Load all commits related to current merge request diff from repo # and save it as array of hashes in st_commits db field - def reload_commits + def save_commits new_attributes = {} - commit_objects = unmerged_commits + commits = compare.commits - if commit_objects.present? - new_attributes[:st_commits] = dump_commits(commit_objects) + if commits.present? + commits = Commit.decorate(commits, merge_request.source_project).reverse + new_attributes[:st_commits] = dump_commits(commits) end update_columns_serialized(new_attributes) end - # Collect array of Git::Diff objects - # between target and source branches - def unmerged_diffs - compare.diffs(Commit.max_diff_options) - end - def dump_diffs(diffs) if diffs.respond_to?(:map) diffs.map(&:to_hash) @@ -162,16 +197,16 @@ class MergeRequestDiff < ActiveRecord::Base end end - # Reload diffs between branches related to current merge request from repo + # Load diffs between branches related to current merge request diff from repo # and save it as array of hashes in st_diffs db field - def reload_diffs + def save_diffs new_attributes = {} new_diffs = [] if commits.size.zero? new_attributes[:state] = :empty else - diff_collection = unmerged_diffs + diff_collection = compare.diffs(Commit.max_diff_options) if diff_collection.overflow? # Set our state to 'overflow' to make the #empty? and #collected? @@ -188,32 +223,17 @@ class MergeRequestDiff < ActiveRecord::Base end new_attributes[:st_diffs] = new_diffs - - new_attributes[:start_commit_sha] = self.target_branch_sha - new_attributes[:head_commit_sha] = self.source_branch_sha - new_attributes[:base_commit_sha] = branch_base_sha - update_columns_serialized(new_attributes) - - keep_around_commits - end - - def project - merge_request.target_project end def repository project.repository end - def branch_base_commit - return unless self.source_branch_sha && self.target_branch_sha - - project.merge_base_commit(self.source_branch_sha, self.target_branch_sha) - end + def find_base_sha + return unless head_commit_sha && start_commit_sha - def branch_base_sha - branch_base_commit.try(:sha) + project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha) end def utf8_st_diffs @@ -248,8 +268,8 @@ class MergeRequestDiff < ActiveRecord::Base end def keep_around_commits - repository.keep_around(target_branch_sha) - repository.keep_around(source_branch_sha) - repository.keep_around(branch_base_sha) + repository.keep_around(start_commit_sha) + repository.keep_around(head_commit_sha) + repository.keep_around(base_commit_sha) end end diff --git a/app/models/project.rb b/app/models/project.rb index 1855760e694208039f33241fc3ac8f0ee5246c7a..0e4fb94f8eb5ddf56b07b25b6e07a1be23e04ba3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -471,8 +471,6 @@ class Project < ActiveRecord::Base end def reset_cache_and_import_attrs - update(import_error: nil) - ProjectCacheWorker.perform_async(self.id) self.import_data.destroy if self.import_data @@ -1037,6 +1035,7 @@ class Project < ActiveRecord::Base "refs/heads/#{branch}", force: true) repository.copy_gitattributes(branch) + repository.expire_avatar_cache(branch) reload_default_branch end diff --git a/app/models/repository.rb b/app/models/repository.rb index bdc3b9d1c1cb5c47809fdb4c274f47e543c86676..91bdafdac99b9f7e394f062f92a4594daf267c49 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1065,7 +1065,7 @@ class Repository @avatar ||= cache.fetch(:avatar) do AVATAR_FILES.find do |file| - blob_at_branch('master', file) + blob_at_branch(root_ref, file) end end end diff --git a/app/models/user.rb b/app/models/user.rb index 48e83ab7e56278a22f3f20cd429d8e5dd884c112..ad3cfbc03e4c6a5cfefc31194107164b4fb34b4e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -489,10 +489,10 @@ class User < ActiveRecord::Base (personal_projects.count.to_f / projects_limit) * 100 end - def recent_push(project_id = nil) + def recent_push(project_ids = nil) # Get push events not earlier than 2 hours ago events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) - events = events.where(project_id: project_id) if project_id + events = events.where(project_id: project_ids) if project_ids # Use the latest event that has not been pushed or merged recently events.recent.find do |event| diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 435a8c6e6819849f6aa1bb3a38a434b42ac57deb..34efd09ed9f64362d4da4523ff3c4e44d8babb54 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -36,7 +36,12 @@ module Boards end def set_state - params[:state] = list.done? ? 'closed' : 'opened' + params[:state] = + case list.list_type.to_sym + when :backlog then 'opened' + when :done then 'closed' + else 'all' + end end def board_label_ids diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 6f7610d42ba0ced02417ff428267fb0a6e9d34fc..f049ed628db19863bcb6c7f6e321780f6c9fb6d8 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -34,7 +34,7 @@ module Ci end def process_build(build, current_status) - return false unless Statuseable::COMPLETED_STATUSES.include?(current_status) + return false unless HasStatus::COMPLETED_STATUSES.include?(current_status) if valid_statuses_for_when(build.when).include?(current_status) build.enqueue diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 546a8f11330e14c23bc0bc429c150816a447c214..0c8446e7c3d6457ed76326bc7ef6f9dc43cdc6d0 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -269,11 +269,11 @@ module SystemNoteService # # Example Note text: # - # "mentioned in #1" + # "Mentioned in #1" # - # "mentioned in !2" + # "Mentioned in !2" # - # "mentioned in 54f7727c" + # "Mentioned in 54f7727c" # # See cross_reference_note_content. # @@ -308,7 +308,7 @@ module SystemNoteService # Check if a cross-reference is disallowed # - # This method prevents adding a "mentioned in !1" note on every single commit + # This method prevents adding a "Mentioned in !1" note on every single commit # in a merge request. Additionally, it prevents the creation of references to # external issues (which would fail). # @@ -417,7 +417,7 @@ module SystemNoteService end def cross_reference_note_prefix - 'mentioned in ' + 'Mentioned in ' end def cross_reference_note_content(gfm_reference) diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index e0ccb65459004da1543048b0f2a3f9d0464f9942..2aab8c736d6a56036de37df5a109620804cc9f73 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -148,7 +148,8 @@ class TodoService def mark_todos_as_done_by_ids(ids, current_user) todos = current_user.todos.where(id: ids) - marked_todos = todos.update_all(state: :done) + # Only return those that are not really on that state + marked_todos = todos.where.not(state: :done).update_all(state: :done) current_user.update_todos_count_cache marked_todos end diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml index 6956e5ab7958a003ae5eead0e8789b5a4daf0e20..bfc6142067a53bf82b91a5256e782ec999997a95 100644 --- a/app/views/admin/system_info/show.html.haml +++ b/app/views/admin/system_info/show.html.haml @@ -9,12 +9,20 @@ .light-well %h4 CPU .data - %h1= "#{@cpus} cores" + - if @cpus + %h1= "#{@cpus.length} cores" + - else + = icon('warning', class: 'text-warning') + Unable to collect CPU info .col-sm-4 .light-well %h4 Memory .data - %h1= "#{number_to_human_size(@mem_used)} / #{number_to_human_size(@mem_total)}" + - if @memory + %h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}" + - else + = icon('warning', class: 'text-warning') + Unable to collect memory info .col-sm-4 .light-well %h4 Disks diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 57f6e7e0612646983d79cab934c96d0f3852efda..b8248a80a275eeafc4d804ce07f07a64b544d4ae 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -24,7 +24,7 @@ - else = sort_title_recently_created %b.caret - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-align-right %li = link_to explore_groups_path(sort: sort_value_recently_created) do = sort_title_recently_created diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 90f362c052be70f24ba822a0d6e8bfaa02cbf5dd..f789796e9429f006e248983bfcb12145f59f116f 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -17,7 +17,7 @@ .panel-heading %strong #{@group.name} group members - %span.badge= @members.size + %span.badge= @members.total_count .controls = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml deleted file mode 100644 index ed3afb0ce3386338fa6359f1991750bbe00b9b9f..0000000000000000000000000000000000000000 --- a/app/views/import/gitorious/status.html.haml +++ /dev/null @@ -1,54 +0,0 @@ -- page_title "Gitorious import" -- header_title "Projects", root_path -%h3.page-title - %i.icon-gitorious.icon-gitorious-big - Import projects from Gitorious.org - -%p.light - Select projects you want to import. -%hr -%p - = button_tag class: "btn btn-import btn-success js-import-all" do - Import all projects - = icon("spinner spin", class: "loading-icon") - -.table-responsive - %table.table.import-jobs - %colgroup.import-jobs-from-col - %colgroup.import-jobs-to-col - %colgroup.import-jobs-status-col - %thead - %tr - %th From Gitorious.org - %th To GitLab - %th Status - %tbody - - @already_added_projects.each do |project| - %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td - = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" - %td - = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] - %td.job-status - - if project.import_status == 'finished' - %span - %i.fa.fa-check - done - - elsif project.import_status == 'started' - %i.fa.fa-spinner.fa-spin - started - - else - = project.human_import_status_name - - - @repos.each do |repo| - %tr{id: "repo_#{repo.id}"} - %td - = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank" - %td.import-target - = repo.full_name - %td.import-actions.job-status - = button_tag class: "btn btn-import js-add-to-import" do - Import - = icon("spinner spin", class: "loading-icon") - -.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } } diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml index 9d925cacc0d2adfef5c06620a6709019a3a7c47f..6bb900e3fc1f34f2b77cfb4ccfc6d47308bb4825 100644 --- a/app/views/projects/commit/_ci_stage.html.haml +++ b/app/views/projects/commit/_ci_stage.html.haml @@ -8,8 +8,8 @@ - if stage = stage.titleize - = render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true - = render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true + = render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true + = render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true %tr %td{colspan: 10} diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 013b05628fae0750eb0f720e6e9c948cd1c662d9..99c71e1454a8dea52e53c92c9066ae1ecf5a3523 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,4 +1,5 @@ - if @merge_request_diff.collected? + = render 'projects/merge_requests/show/versions' = render "projects/diffs/diffs", diffs: @diffs - 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/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..2da70ce7137adba857a99d4eba2d13266f8260ac --- /dev/null +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -0,0 +1,31 @@ +- merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff + +- if merge_request_diffs.size > 1 + .mr-version-switch + Version: + %span.dropdown.inline + %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } + %strong.monospace< + - if @merge_request_diff.latest? + #{"latest"} + - else + #{@merge_request_diff.head_commit.short_id} + %span.caret + %ul.dropdown-menu.dropdown-menu-selectable + - merge_request_diffs.each do |merge_request_diff| + %li + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do + %strong.monospace + #{merge_request_diff.head_commit.short_id} + %br + %small + #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, + = time_ago_with_tooltip(merge_request_diff.created_at) + + - unless @merge_request_diff.latest? + %span.prepend-left-default + = icon('info-circle') + This version is not the latest one. Comments are disabled + .pull-right + %span.monospace + #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index ea4898f2107fa678044c84cb38670bb2ead09170..0a1e2bb2cc6632ecb50ffe49153c523da66fe4fe 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -59,11 +59,6 @@ = icon('gitlab', text: 'GitLab.com') - unless gitlab_import_configured? = render 'gitlab_import_modal' - %div - - if gitorious_import_enabled? - = link_to new_import_gitorious_path, class: 'btn import_gitorious' do - %i.icon-gitorious.icon-gitorious-small - Gitorious.org %div - if google_code_import_enabled? = link_to new_import_google_code_path, class: 'btn import_google_code' do diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index d2ac1ce2b9a9c001581d0e1834f9fb69a0140f02..7c82177f9ea390ce50173ae7e4f1aaf6ceb8bc41 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -52,11 +52,11 @@ - if note.emoji_awardable? = link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do = icon('spinner spin') - = icon('smile-o') + = icon('smile-o', class: 'link-highlight') - if note_editable = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do - = icon('pencil') + = icon('pencil', class: 'link-highlight') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do = icon('trash-o') .note-body{class: note_editable ? 'js-task-list-container' : ''} diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index e6701078f71f86b972cb02d268127e5ab7a40f8e..d2ca8813ab9db2a955b037e3af95df7449f788bf 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -14,6 +14,8 @@ class RepositoryImportWorker import_url: @project.import_url, path: @project.path_with_namespace) + project.update_column(:import_error, nil) + result = Projects::ImportService.new(project, current_user).execute if result[:status] == :error diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 7a9376def02634dbb11aa5689b9f9c2a8cb8d9f5..4a01b9e40fb3f4ca3b0fd39dc2a1bb9ca8c585d5 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -212,7 +212,7 @@ Settings.gitlab.default_projects_features['builds'] = true if Settin Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil? Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['domain_whitelist'] ||= [] -Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project] +Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project] Settings.gitlab['trusted_proxies'] ||= [] # diff --git a/config/routes.rb b/config/routes.rb index e93b640fbc0958a413da4af9ddfc125bfb19a0a9..24f9b44a53a4e59fb8c31daf4d3524b43ec84776 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -157,12 +157,6 @@ Rails.application.routes.draw do get :jobs end - resource :gitorious, only: [:create, :new], controller: :gitorious do - get :status - get :callback - get :jobs - end - resource :google_code, only: [:create, :new], controller: :google_code do get :status post :callback diff --git a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb new file mode 100644 index 0000000000000000000000000000000000000000..c8cbd2718ff0825038d3d78a6c426427049e5909 --- /dev/null +++ b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb @@ -0,0 +1,21 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class MergeRequestDiffRemoveUniq < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + if index_exists?(:merge_request_diffs, :merge_request_id) + remove_index :merge_request_diffs, :merge_request_id + end + end + + def down + unless index_exists?(:merge_request_diffs, :merge_request_id) + add_concurrent_index :merge_request_diffs, :merge_request_id, unique: true + end + end +end diff --git a/db/migrate/20160725104452_merge_request_diff_add_index.rb b/db/migrate/20160725104452_merge_request_diff_add_index.rb new file mode 100644 index 0000000000000000000000000000000000000000..6d04242dd25af8e410da13ec58d03e4023ef4f6a --- /dev/null +++ b/db/migrate/20160725104452_merge_request_diff_add_index.rb @@ -0,0 +1,17 @@ +class MergeRequestDiffAddIndex < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + add_concurrent_index :merge_request_diffs, :merge_request_id + end + + def down + if index_exists?(:merge_request_diffs, :merge_request_id) + remove_index :merge_request_diffs, :merge_request_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 802c928b2fc8c77ca257b8438051208acfb1dd45..5a105a91ad1bf25ee8d4b9e7e50df78a2601caee 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -593,7 +593,7 @@ ActiveRecord::Schema.define(version: 20160823081327) do t.string "start_commit_sha" end - add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree + add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", using: :btree create_table "merge_requests", force: :cascade do |t| t.string "target_branch", null: false diff --git a/doc/README.md b/doc/README.md index 195b7c7af62c7f915791af7c261a30ab3fe02a95..254394eb63e7e027df8148cc820eef6d1171328b 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,6 +2,7 @@ ## User documentation +- [Account Security](user/account/security.md) Securing your account via two-factor authentication, etc. - [API](api/README.md) Automate GitLab via a simple and powerful API. - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. @@ -18,7 +19,6 @@ - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. -- [Koding](user/project/koding.md) Learn how to use Koding, the online IDE. ## Administrator documentation diff --git a/doc/api/issues.md b/doc/api/issues.md index a665645ad0ef618be6f51dc8d6b218891958693a..b194799ccbf93215d087c17294066d0939f6b02d 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -79,7 +79,8 @@ Example response: "labels" : [], "subscribed" : false, "user_notes_count": 1, - "due_date": "2016-07-22" + "due_date": "2016-07-22", + "web_url": "http://example.com/example/example/issues/6" } ] ``` @@ -156,7 +157,8 @@ Example response: "created_at" : "2016-01-04T15:31:46.176Z", "subscribed" : false, "user_notes_count": 1, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/1" } ] ``` @@ -235,7 +237,8 @@ Example response: "created_at" : "2016-01-04T15:31:46.176Z", "subscribed" : false, "user_notes_count": 1, - "due_date": "2016-07-22" + "due_date": "2016-07-22", + "web_url": "http://example.com/example/example/issues/1" } ] ``` @@ -299,7 +302,8 @@ Example response: "created_at" : "2016-01-04T15:31:46.176Z", "subscribed": false, "user_notes_count": 1, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/1" } ``` @@ -323,8 +327,8 @@ POST /projects/:id/issues | `assignee_id` | integer | no | The ID of a user to assign issue | | `milestone_id` | integer | no | The ID of a milestone to assign issue | | `labels` | string | no | Comma-separated label names for an issue | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | -| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | +| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | +| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug @@ -357,7 +361,8 @@ Example response: "milestone" : null, "subscribed" : true, "user_notes_count": 0, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/14" } ``` @@ -384,8 +389,8 @@ PUT /projects/:id/issues/:issue_id | `milestone_id` | integer | no | The ID of a milestone to assign the issue to | | `labels` | string | no | Comma-separated label names for an issue | | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | -| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | -| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | +| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | +| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close @@ -418,7 +423,8 @@ Example response: "milestone" : null, "subscribed" : true, "user_notes_count": 0, - "due_date": "2016-07-22" + "due_date": "2016-07-22", + "web_url": "http://example.com/example/example/issues/15" } ``` @@ -496,7 +502,8 @@ Example response: "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", "web_url": "https://gitlab.example.com/u/solon.cremin" }, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/11" } ``` @@ -551,7 +558,8 @@ Example response: "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", "web_url": "https://gitlab.example.com/u/solon.cremin" }, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/11" } ``` @@ -607,7 +615,8 @@ Example response: "web_url": "https://gitlab.example.com/u/orville" }, "subscribed": false, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/12" } ``` @@ -693,7 +702,9 @@ Example response: "subscribed": true, "user_notes_count": 7, "upvotes": 0, - "downvotes": 0 + "downvotes": 0, + "due_date": null, + "web_url": "http://example.com/example/example/issues/110" }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10", "body": "Vel voluptas atque dicta mollitia adipisci qui at.", diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 3e88a7589365942c256c13bbbcaf4276ea41a25e..f4760ceac7c7152f6e96afe7cd8066baf70a1b86 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -70,7 +70,8 @@ Parameters: "subscribed" : false, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ] ``` @@ -136,7 +137,8 @@ Parameters: "subscribed" : true, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -239,6 +241,7 @@ Parameters: "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1", "changes": [ { "old_path": "VERSION", @@ -321,7 +324,8 @@ Parameters: "subscribed" : true, "user_notes_count": 0, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -395,7 +399,8 @@ Parameters: "subscribed" : true, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -496,7 +501,8 @@ Parameters: "subscribed" : true, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -565,7 +571,8 @@ Parameters: "subscribed" : true, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -886,7 +893,8 @@ Example response: "subscribed": true, "user_notes_count": 7, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7", "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.", @@ -894,3 +902,112 @@ Example response: "created_at": "2016-07-01T11:14:15.530Z" } ``` + +## Get MR diff versions + +Get a list of merge request diff versions. + +``` +GET /projects/:id/merge_requests/:merge_request_id/versions +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | String | yes | The ID of the project | +| `merge_request_id` | integer | yes | The ID of the merge request | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions +``` + +Example response: + +```json +[{ + "id": 110, + "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-26T14:44:48.926Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1" +}, { + "id": 108, + "head_commit_sha": "3eed087b29835c48015768f839d76e5ea8f07a24", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-25T14:21:33.028Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1" +}] +``` + +## Get a single MR diff version + +Get a single merge request diff version. + +``` +GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | String | yes | The ID of the project | +| `merge_request_id` | integer | yes | The ID of the merge request | +| `version_id` | integer | yes | The ID of the merge request diff version | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions/1 +``` + +Example response: + +```json +{ + "id": 110, + "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-26T14:44:48.926Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1", + "commits": [{ + "id": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "short_id": "33e2ee85", + "title": "Change year to 2018", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-26T17:44:29.000+03:00", + "message": "Change year to 2018" + }, { + "id": "aa24655de48b36335556ac8a3cd8bb521f977cbd", + "short_id": "aa24655d", + "title": "Update LICENSE", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-25T17:21:53.000+03:00", + "message": "Update LICENSE" + }, { + "id": "3eed087b29835c48015768f839d76e5ea8f07a24", + "short_id": "3eed087b", + "title": "Add license", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-25T17:21:20.000+03:00", + "message": "Add license" + }], + "diffs": [{ + "old_path": "LICENSE", + "new_path": "LICENSE", + "a_mode": "0", + "b_mode": "100644", + "diff": "--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,21 @@\n+The MIT License (MIT)\n+\n+Copyright (c) 2018 Administrator\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to whom the Software is\n+furnished to do so, subject to the following conditions:\n+\n+The above copyright notice and this permission notice shall be included in all\n+copies or substantial portions of the Software.\n+\n+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+SOFTWARE.\n", + "new_file": true, + "renamed_file": false, + "deleted_file": false + }] +} +``` diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md index a7acf37b5bcc05b9e81fae2e1849b7d39607e4cf..c6685f54a9d297d26499754524d85b0b6db4061a 100644 --- a/doc/api/project_snippets.md +++ b/doc/api/project_snippets.md @@ -53,7 +53,8 @@ Parameters: }, "expires_at": null, "updated_at": "2012-06-28T10:52:04Z", - "created_at": "2012-06-28T10:52:04Z" + "created_at": "2012-06-28T10:52:04Z", + "web_url": "http://example.com/example/example/snippets/1" } ``` diff --git a/doc/api/projects.md b/doc/api/projects.md index 37d97b2db44691b66a02b54b2aa090c7bd9e92ab..0e4806e31c51810229e99704dc33640bf27b6de0 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -84,7 +84,8 @@ Parameters: "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false }, { "id": 6, @@ -144,7 +145,8 @@ Parameters: "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ] ``` @@ -280,7 +282,8 @@ Parameters: "group_name": "Gitlab Org", "group_access_level": 10 } - ] + ], + "only_allow_merge_if_build_succeeds": false } ``` @@ -448,6 +451,7 @@ Parameters: - `visibility_level` (optional) - `import_url` (optional) - `public_builds` (optional) +- `only_allow_merge_if_build_succeeds` (optional) ### Create project for user @@ -473,6 +477,7 @@ Parameters: - `visibility_level` (optional) - `import_url` (optional) - `public_builds` (optional) +- `only_allow_merge_if_build_succeeds` (optional) ### Edit project @@ -499,6 +504,7 @@ Parameters: - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `public_builds` (optional) +- `only_allow_merge_if_build_succeeds` (optional) On success, method returns 200 with the updated project. If parameters are invalid, 400 is returned. @@ -577,7 +583,8 @@ Example response: "forks_count": 0, "star_count": 1, "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ``` @@ -643,7 +650,8 @@ Example response: "forks_count": 0, "star_count": 0, "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ``` @@ -729,7 +737,8 @@ Example response: "star_count": 0, "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ``` @@ -815,7 +824,8 @@ Example response: "star_count": 0, "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ``` @@ -914,7 +924,11 @@ Parameters: "push_events": true, "issues_events": true, "merge_requests_events": true, + "tag_push_events": true, "note_events": true, + "build_events": true, + "pipeline_events": true, + "wiki_page_events": true, "enable_ssl_verification": true, "created_at": "2012-10-12T17:04:47Z" } @@ -937,6 +951,9 @@ Parameters: - `merge_requests_events` - Trigger hook on merge_requests events - `tag_push_events` - Trigger hook on push_tag events - `note_events` - Trigger hook on note events +- `build_events` - Trigger hook on build events +- `pipeline_events` - Trigger hook on pipeline events +- `wiki_page_events` - Trigger hook on wiki page events - `enable_ssl_verification` - Do SSL verification when triggering the hook ### Edit project hook @@ -957,6 +974,9 @@ Parameters: - `merge_requests_events` - Trigger hook on merge_requests events - `tag_push_events` - Trigger hook on push_tag events - `note_events` - Trigger hook on note events +- `build_events` - Trigger hook on build events +- `pipeline_events` - Trigger hook on pipeline events +- `wiki_page_events` - Trigger hook on wiki page events - `enable_ssl_verification` - Do SSL verification when triggering the hook ### Delete project hook diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 927a18724135411c679050b6a36f7cba28004b12..dc7d832ce3acd65d262d2eebeced45518c8b52f8 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -6,7 +6,7 @@ it organized and easy to find. ## Location and naming of documents >**Note:** -These guidelines derive from the discussion taken place in issue [#3349](ce-3349). +These guidelines derive from the discussion taken place in issue [#3349][ce-3349]. The documentation hierarchy can be vastly improved by providing a better layout and organization of directories. diff --git a/doc/install/installation.md b/doc/install/installation.md index b0ec9b4a9611de3c2666858f94a421eacaba7396..d4b89fa834562e9c8974bb7bb4e7a29db44a89e5 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -397,7 +397,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.10 + sudo -u git -H git checkout v0.7.11 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/install/requirements.md b/doc/install/requirements.md index a65ac8a5f79adc5c6736de05efa39506f1e7ce9c..571f1a38358a620c257747d18a7f905f12e16506 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -32,7 +32,7 @@ Please consider using a virtual machine to run GitLab. ## Ruby versions -GitLab requires Ruby (MRI) 2.1.x and currently does not work with versions 2.2 or 2.3. +GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13. You will have to use the standard MRI implementation of Ruby. We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab diff --git a/doc/integration/README.md b/doc/integration/README.md index 70895abbcadc3bb365ebd9fd0ffc254e790f9b39..c2fd299db07a1240770433265f83ba42f2e73cca 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -15,7 +15,7 @@ See the documentation below for details on how to configure these services. - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages - [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users - [Akismet](akismet.md) Configure Akismet to stop spam -- [Koding](koding.md) Configure Koding to use IDE integration +- [Koding](../administration/integration/koding.md) Configure Koding to use IDE integration GitLab Enterprise Edition contains [advanced Jenkins support][jenkins]. diff --git a/doc/user/account/security.md b/doc/user/account/security.md new file mode 100644 index 0000000000000000000000000000000000000000..816094bf8d2e33135b680511c21cc786b8eac29a --- /dev/null +++ b/doc/user/account/security.md @@ -0,0 +1,3 @@ +# Account Security + +- [Two-Factor Authentication](two_factor_authentication.md) diff --git a/doc/user/account/two_factor_authentication.md b/doc/user/account/two_factor_authentication.md new file mode 100644 index 0000000000000000000000000000000000000000..881358ed94dd9156913b57f9597172b09e3dd89e --- /dev/null +++ b/doc/user/account/two_factor_authentication.md @@ -0,0 +1,68 @@ +# Two-Factor Authentication + +## Recovery options + +If you lose your code generation device (such as your mobile phone) and you need +to disable two-factor authentication on your account, you have several options. + +### Use a saved recovery code + +When you enabled two-factor authentication for your account, a series of +recovery codes were generated. If you saved those codes somewhere safe, you +may use one to sign in. + +First, enter your username/email and password on the GitLab sign in page. When +prompted for a two-factor code, enter one of the recovery codes you saved +previously. + +> **Note:** Once a particular recovery code has been used, it cannot be used again. + You may still use the other saved recovery codes at a later time. + +### Generate new recovery codes using SSH + +It's not uncommon for users to forget to save the recovery codes when enabling +two-factor authentication. If you have an SSH key added to your GitLab account, +you can generate a new set of recovery codes using SSH. + +Run `ssh git@gitlab.example.com 2fa_recovery_codes`. You will be prompted to +confirm that you wish to generate new codes. If you choose to continue, any +previously saved codes will be invalidated. + +```bash +$ ssh git@gitlab.example.com 2fa_recovery_codes +Are you sure you want to generate new two-factor recovery codes? +Any existing recovery codes you saved will be invalidated. (yes/no) +yes + +Your two-factor authentication recovery codes are: + +119135e5a3ebce8e +11f6v2a498810dcd +3924c7ab2089c902 +e79a3398bfe4f224 +34bd7b74adbc8861 +f061691d5107df1a +169bf32a18e63e7f +b510e7422e81c947 +20dbed24c5e74663 +df9d3b9403b9c9f0 + +During sign in, use one of the codes above when prompted for +your two-factor code. Then, visit your Profile Settings and add +a new device so you do not lose access to your account again. +``` + +Next, go to the GitLab sign in page and enter your username/email and password. +When prompted for a two-factor code, enter one of the recovery codes obtained +from the command line output. + +> **Note:** After signing in, you should immediately visit your **Profile Settings + -> Account** to set up two-factor authentication with a new device. + +### Ask a GitLab administrator to disable two-factor on your account + +If the above two methods are not possible, you may ask a GitLab global +administrator to disable two-factor authentication for your account. Please +be aware that this will temporarily leave your account in a less secure state. +You should sign in and re-enable two-factor authentication as soon as possible +after the administrator disables it. diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png index accd0900cbbda0d74e19cc41ef545b827ffe243b..63c269f6dbc41a8f9d8db31f485ae95f012473c5 100644 Binary files a/doc/user/project/img/issue_board.png and b/doc/user/project/img/issue_board.png differ diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 9df9ed9c9da1d1eaa10d6715afc2ff6ef9bbdfdd..cac926b3e28fc055747881809ffb467e8198cf28 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -31,10 +31,9 @@ Below is a table of the definitions used for GitLab's Issue Board. There are three types of lists, the ones you create based on your labels, and two default: -- **Backlog** (default): shows all issues that do not fall in one of the other - lists. Always appears on the very left. -- **Done** (default): shows all closed issues. Always appears on the very right. -- Label list: a list based on a label. It shows all issues with that label. +- **Backlog** (default): shows all opened issues that do not fall in one of the other lists. Always appears on the very left. +- **Done** (default): shows all closed issues that do not fall in one of the other lists. Always appears on the very right. +- Label list: a list based on a label. It shows all opened or closed issues with that label.  diff --git a/doc/user/project/merge_requests/resolve_conflicts.md b/doc/user/project/merge_requests/resolve_conflicts.md index 44b76ffc8e689e867b2be786f8e8fb8bd6536f97..4d7225bd820dd7ac334cd40d7425b30b2ddc2cc6 100644 --- a/doc/user/project/merge_requests/resolve_conflicts.md +++ b/doc/user/project/merge_requests/resolve_conflicts.md @@ -26,6 +26,7 @@ this is similar to performing `git checkout feature; git merge master` locally. GitLab allows resolving conflicts in a file where all of the below are true: - The file is text, not binary +- The file is in a UTF-8 compatible encoding - The file does not already contain conflict markers - The file, with conflict markers added, is not over 200 KB in size - The file exists under the same path in both branches diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 8119324bb62055bd6260554f3af00916a47d5272..7c0eb90d540e85ca0983370311630cb2b80ddd46 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -115,7 +115,7 @@ In this flow it is not common to have a production branch (or git flow master br Merge or pull requests are created in a git management application and ask an assigned person to merge two branches. Tools such as GitHub and Bitbucket choose the name pull request since the first manual action would be to pull the feature branch. -Tools such as GitLab and Gitorious choose the name merge request since that is the final action that is requested of the assignee. +Tools such as GitLab and others choose the name merge request since that is the final action that is requested of the assignee. In this article we'll refer to them as merge requests. If you work on a feature branch for more than a few hours it is good to share the intermediate result with the rest of the team. diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index d2ec56e650456cf173004b3ceacce8d0d8bfcddc..40a5e4476be1f67657b6ec060895c9bcde2bd393 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -15,6 +15,25 @@ Please note that you need to have builds configured to enable this feature. ## Checkout merge requests locally +### By adding a git alias + +Add the following alias to your `~/.gitconfig`: + +``` +[alias] + mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' - +``` + +Now you can check out a particular merge request from any repository and any remote, e.g. to check out a merge request number 5 as shown in GitLab from the `upstream` remote, do: + +``` +$ git mr upstream 5 +``` + +This will fetch the merge request into a local `mr-upstream-5` branch and check it out. + +### By modifying `.git/config` for a given repository + Locate the section for your GitLab remote in the `.git/config` file. It looks like this: ``` @@ -34,7 +53,7 @@ It should look like this: fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/* ``` -Now you can fetch all the merge requests requests: +Now you can fetch all the merge requests: ``` $ git fetch origin @@ -61,3 +80,11 @@ If you click the "Hide whitespace changes" button, you can see the diff without It is also working on commits compare view.  + +## Merge Requests versions + +Every time you push to merge request branch, a new version of merge request diff +is created. When you visit the merge request page you see latest version of changes. +However you can select an older one from version dropdown + + diff --git a/doc/workflow/merge_requests/versions.png b/doc/workflow/merge_requests/versions.png new file mode 100644 index 0000000000000000000000000000000000000000..c0a6dfe6806a0baf710b4c1f93fb10836ed7ce8d Binary files /dev/null and b/doc/workflow/merge_requests/versions.png differ diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 967f2edb243f8f693381d496d4076e967d064085..5aa592e9067ccc9d8cc9f49af8c0e46535b06ad8 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -24,7 +24,7 @@ Feature: Project Merge Requests Scenario: I should see target branch when it is different from default Given project "Shop" have "Bug NS-06" open merge request When I visit project "Shop" merge requests page - Then I should see "other_branch" branch + Then I should see "feature_conflict" branch Scenario: I should not see the numbers of diverged commits if the branch is rebased on the target Given project "Shop" have "Bug NS-07" open merge request with rebased branch diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index f0d8d498e4623b7e220621ca102f5adec1c0abf0..2f0941e4113df348ddf949ef6ce3718e097d3e37 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -18,7 +18,6 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps expect(page).to have_link('GitHub') expect(page).to have_link('Bitbucket') expect(page).to have_link('GitLab.com') - expect(page).to have_link('Gitorious.org') expect(page).to have_link('Google Code') expect(page).to have_link('Repo by URL') end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 9778ff4a6c7d89e5266a31807198df25519686ac..56b289495858ddde4c21dd73e202a3c760673341 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -58,8 +58,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps expect(find('.merge-request-info')).not_to have_content "master" end - step 'I should see "other_branch" branch' do - expect(page).to have_content "other_branch" + step 'I should see "feature_conflict" branch' do + expect(page).to have_content "feature_conflict" end step 'I should see "Bug NS-04" in merge requests' do @@ -124,7 +124,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps source_project: project, target_project: project, source_branch: 'fix', - target_branch: 'other_branch', + target_branch: 'feature_conflict', author: project.users.first, description: "# Description header" ) diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index aa666a954bca40aed5e359a1b44c8b9c3bee039c..df9845ba569dac0788ce79c6e311246af309a87e 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -179,7 +179,7 @@ module SharedIssuable project = Project.find_by(name: from_project_name) expect(page).to have_content(user_name) - expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") + expect(page).to have_content("Mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") end def expect_sidebar_content(content) diff --git a/lib/api/api.rb b/lib/api/api.rb index 6b8bfbbdae6110e5fa5067653d10987d18b77d67..4602e627fdb71b39c1f3b5ee3ccd54eb4c17aaf4 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -18,22 +18,14 @@ module API end rescue_from :all do |exception| - # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 - # why is this not wrapped in something reusable? - trace = exception.backtrace - - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << trace.join("\n ") - - API.logger.add Logger::FATAL, message - rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500) + handle_api_exception(exception) end format :json content_type :txt, "text/plain" # Ensure the namespace is right, otherwise we might load Grape::API::Helpers + helpers ::SentryHelper helpers ::API::Helpers mount ::API::AccessRequests @@ -75,5 +67,6 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + mount ::API::MergeRequestDiffs end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 66b85ab17935e1fc25434d469dc293afffec1853..cbb324dd06d63d48066c28a035978193beecc738 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -49,7 +49,7 @@ module API class ProjectHook < Hook expose :project_id, :push_events expose :issues_events, :merge_requests_events, :tag_push_events - expose :note_events, :build_events, :pipeline_events + expose :note_events, :build_events, :pipeline_events, :wiki_page_events expose :enable_ssl_verification end @@ -90,6 +90,7 @@ module API expose :shared_with_groups do |project, options| SharedGroup.represent(project.project_group_links.all, options) end + expose :only_allow_merge_if_build_succeeds end class Member < UserBasic @@ -177,6 +178,10 @@ module API # TODO (rspeicher): Deprecated; remove in 9.0 expose(:expires_at) { |snippet| nil } + + expose :web_url do |snippet, options| + Gitlab::UrlBuilder.build(snippet) + end end class ProjectEntity < Grape::Entity @@ -206,6 +211,10 @@ module API expose :user_notes_count expose :upvotes, :downvotes expose :due_date + + expose :web_url do |issue, options| + Gitlab::UrlBuilder.build(issue) + end end class ExternalIssue < Grape::Entity @@ -229,6 +238,10 @@ module API expose :user_notes_count expose :should_remove_source_branch?, as: :should_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch + + expose :web_url do |merge_request, options| + Gitlab::UrlBuilder.build(merge_request) + end end class MergeRequestChanges < MergeRequest @@ -237,6 +250,19 @@ module API end end + class MergeRequestDiff < Grape::Entity + expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha, + :created_at, :merge_request_id, :state, :real_size + end + + class MergeRequestDiffFull < MergeRequestDiff + expose :commits, using: Entities::RepoCommit + + expose :diffs, using: Entities::RepoDiff do |compare, _| + compare.raw_diffs(all_diffs: true).to_a + end + end + class SSHKey < Grape::Entity expose :id, :title, :key, :created_at end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index d0469d6602d0df0feb595a1285a0c8420703c02b..da4b1bf9902c8493fcaa3fb49dd96e79efad0622 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -279,6 +279,24 @@ module API error!({ 'message' => message }, status) end + def handle_api_exception(exception) + if sentry_enabled? && report_exception?(exception) + define_params_for_grape_middleware + sentry_context + Raven.capture_exception(exception) + end + + # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 + trace = exception.backtrace + + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << trace.join("\n ") + + API.logger.add Logger::FATAL, message + rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500) + end + # Projects helpers def filter_projects(projects) @@ -419,5 +437,19 @@ module API Entities::Issue end end + + # The Grape Error Middleware only has access to env but no params. We workaround this by + # defining a method that returns the right value. + def define_params_for_grape_middleware + self.define_singleton_method(:params) { Rack::Request.new(env).params.symbolize_keys } + end + + # We could get a Grape or a standard Ruby exception. We should only report anything that + # is clearly an error. + def report_exception?(exception) + return true unless exception.respond_to?(:status) + + exception.status == 500 + end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index d8e9ac406c4c4067df971508e6c52af123a35224..5b54c11ef62a84c9613b2035bae97147af0faa45 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -101,6 +101,31 @@ module API {} end end + + post '/two_factor_recovery_codes' do + status 200 + + key = Key.find(params[:key_id]) + user = key.user + + # Make sure this isn't a deploy key + unless key.type.nil? + return { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } + end + + unless user.present? + return { success: false, message: 'Could not find a user for the given key' } + end + + unless user.two_factor_enabled? + return { success: false, message: 'Two-factor authentication is not enabled for this user' } + end + + codes = user.generate_otp_backup_codes! + user.save! + + { success: true, recovery_codes: codes } + end end end end diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb new file mode 100644 index 0000000000000000000000000000000000000000..07435d78468ea343777a66303fc611bba9f9feb1 --- /dev/null +++ b/lib/api/merge_request_diffs.rb @@ -0,0 +1,45 @@ +module API + # MergeRequestDiff API + class MergeRequestDiffs < Grape::API + before { authenticate! } + + resource :projects do + desc 'Get a list of merge request diff versions' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::MergeRequestDiff + end + + params do + requires :id, type: String, desc: 'The ID of a project' + requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' + end + + get ":id/merge_requests/:merge_request_id/versions" do + merge_request = user_project.merge_requests. + find(params[:merge_request_id]) + + authorize! :read_merge_request, merge_request + present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff + end + + desc 'Get a single merge request diff version' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::MergeRequestDiffFull + end + + params do + requires :id, type: String, desc: 'The ID of a project' + requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' + requires :version_id, type: Integer, desc: 'The ID of a merge request diff version' + end + + get ":id/merge_requests/:merge_request_id/versions/:version_id" do + merge_request = user_project.merge_requests. + find(params[:merge_request_id]) + + authorize! :read_merge_request, merge_request + present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull + end + end + end +end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 3f63cd678e8f3ae2fa5295911af695f6db7aeeba..14f5be3b5f64bef5a0cc2e8bfba22eda9c276863 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -46,6 +46,7 @@ module API :note_events, :build_events, :pipeline_events, + :wiki_page_events, :enable_ssl_verification ] @hook = user_project.hooks.new(attrs) @@ -80,6 +81,7 @@ module API :note_events, :build_events, :pipeline_events, + :wiki_page_events, :enable_ssl_verification ] diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 60cfc103afd27f94a8b5afc360f970a41c7fca4a..71efd4f33ca6d2c5d0905e8ee58c17c0b1668f79 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -123,7 +123,8 @@ module API :public, :visibility_level, :import_url, - :public_builds] + :public_builds, + :only_allow_merge_if_build_succeeds] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(current_user, attrs).execute if @project.saved? @@ -172,7 +173,8 @@ module API :public, :visibility_level, :import_url, - :public_builds] + :public_builds, + :only_allow_merge_if_build_succeeds] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(user, attrs).execute if @project.saved? @@ -234,7 +236,8 @@ module API :shared_runners_enabled, :public, :visibility_level, - :public_builds] + :public_builds, + :only_allow_merge_if_build_succeeds] attrs = map_public_to_visibility_level(attrs) authorize_admin_project authorize! :rename_project, user_project if attrs[:name].present? diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb index 17bb99a2ae50248da39cb42fe1e545daa8b7f57e..a6b9beecded18128ccd4ee5d3b7217e05918edff 100644 --- a/lib/ci/api/api.rb +++ b/lib/ci/api/api.rb @@ -9,22 +9,14 @@ module Ci end rescue_from :all do |exception| - # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 - # why is this not wrapped in something reusable? - trace = exception.backtrace - - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << trace.join("\n ") - - API.logger.add Logger::FATAL, message - rack_response({ 'message' => '500 Internal Server Error' }, 500) + handle_api_exception(exception) end content_type :txt, 'text/plain' content_type :json, 'application/json' format :json + helpers ::SentryHelper helpers ::Ci::API::Helpers helpers ::API::Helpers helpers Gitlab::CurrentSettings diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index a293fa2752f8ab7878d4cfb7c358049f2b649247..a4558d157c0480408bd6b18433e7f6f22cc62588 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -94,9 +94,7 @@ module ExtractsPath @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? } @options = HashWithIndifferentAccess.new(@options) - @id = params[:id] || params[:ref] - @id += "/" + params[:path] unless params[:path].blank? - + @id = get_id @ref, @path = extract_ref(@id) @repo = @project.repository if @options[:extended_sha1].blank? @@ -118,4 +116,13 @@ module ExtractsPath def tree @tree ||= @repo.tree(@commit.id, @path) end + + private + + # overriden in subclasses, do not remove + def get_id + id = params[:id] || params[:ref] + id += "/" + params[:path] unless params[:path].blank? + id + end end diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 0a1fd27ced542e1e8c8848217f6d96eb97187075..dff9e29c6a5f8fdf0562c6ab0616405e802de26d 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -181,6 +181,17 @@ module Gitlab sections: sections } end + + # Don't try to print merge_request or repository. + def inspect + instance_variables = [:merge_file_result, :their_path, :our_path, :our_mode].map do |instance_variable| + value = instance_variable_get("@#{instance_variable}") + + "#{instance_variable}=\"#{value}\"" + end + + "#<#{self.class} #{instance_variables.join(' ')}>" + end end end end diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 6eccded7872791b5c6fab5ec2da4a1dc5e8f01fb..2d4d55daeeb998be824eed7385e62ac7b29f36fd 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -13,10 +13,19 @@ module Gitlab class UnmergeableFile < ParserError end + class UnsupportedEncoding < ParserError + end + def parse(text, our_path:, their_path:, parent_file: nil) raise UnmergeableFile if text.blank? # Typically a binary file raise UnmergeableFile if text.length > 102400 + begin + text.to_json + rescue Encoding::UndefinedConversionError + raise UnsupportedEncoding + end + line_obj_index = 0 line_old = 1 line_new = 1 diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index 9dc2602867e07da8731da1eedb2cb31796a03596..bd681f03173a41b71c0b552543371a91e8828cad 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -23,7 +23,6 @@ module Gitlab dates.each do |date| date_id = date.to_time.to_i.to_s - @timestamps[date_id] = 0 day_events = events.find { |day_events| day_events["date"] == date } if day_events diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 27acd817e516511ee304fefd3f465a8d79ad28a1..12fbb78c53e2e56f723b862fbdd5af654ccce9b6 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -41,7 +41,7 @@ module Gitlab default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project], + import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], require_two_factor_authentication: false, diff --git a/lib/gitlab/diff/file_collection/merge_request.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb similarity index 83% rename from lib/gitlab/diff/file_collection/merge_request.rb rename to lib/gitlab/diff/file_collection/merge_request_diff.rb index 4f946908e2f9c63eb42ad71030d0511ad71402f3..36348b339430d4fe9be533d2bb51426000a3146e 100644 --- a/lib/gitlab/diff/file_collection/merge_request.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -1,14 +1,14 @@ module Gitlab module Diff module FileCollection - class MergeRequest < Base - def initialize(merge_request, diff_options:) - @merge_request = merge_request + class MergeRequestDiff < Base + def initialize(merge_request_diff, diff_options:) + @merge_request_diff = merge_request_diff - super(merge_request, - project: merge_request.project, + super(merge_request_diff, + project: merge_request_diff.project, diff_options: diff_options, - diff_refs: merge_request.diff_refs) + diff_refs: merge_request_diff.diff_refs) end def diff_files @@ -61,11 +61,11 @@ module Gitlab end def cacheable? - @merge_request.merge_request_diff.present? + @merge_request_diff.present? end def cache_key - [@merge_request.merge_request_diff, 'highlighted-diff-files', diff_options] + [@merge_request_diff, 'highlighted-diff-files', diff_options] end end end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 9ddc8905bd6149f32bf58fbb896afc45787613c5..02ffb43d89bd42e3c4da8ca049cd02d9e5cc7bfb 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -3,12 +3,13 @@ module Gitlab class Importer include Gitlab::ShellAdapter - attr_reader :client, :project, :repo, :repo_url + attr_reader :client, :errors, :project, :repo, :repo_url def initialize(project) @project = project @repo = project.import_source @repo_url = project.import_url + @errors = [] if credentials @client = Client.new(credentials[:user]) @@ -18,8 +19,14 @@ module Gitlab end def execute - import_labels && import_milestones && import_issues && - import_pull_requests && import_wiki + import_labels + import_milestones + import_issues + import_pull_requests + import_wiki + handle_errors + + true end private @@ -28,22 +35,37 @@ module Gitlab @credentials ||= project.import_data.credentials if project.import_data end + def handle_errors + return unless errors.any? + + project.update_column(:import_error, { + message: 'The remote data could not be fully imported.', + errors: errors + }.to_json) + end + def import_labels labels = client.labels(repo, per_page: 100) - labels.each { |raw| LabelFormatter.new(project, raw).create! } - true - rescue ActiveRecord::RecordInvalid => e - raise Projects::ImportService::Error, e.message + labels.each do |raw| + begin + LabelFormatter.new(project, raw).create! + rescue => e + errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end + end end def import_milestones milestones = client.milestones(repo, state: :all, per_page: 100) - milestones.each { |raw| MilestoneFormatter.new(project, raw).create! } - true - rescue ActiveRecord::RecordInvalid => e - raise Projects::ImportService::Error, e.message + milestones.each do |raw| + begin + MilestoneFormatter.new(project, raw).create! + rescue => e + errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end + end end def import_issues @@ -53,15 +75,15 @@ module Gitlab gh_issue = IssueFormatter.new(project, raw) if gh_issue.valid? - issue = gh_issue.create! - apply_labels(issue) - import_comments(issue) if gh_issue.has_comments? + begin + issue = gh_issue.create! + apply_labels(issue) + import_comments(issue) if gh_issue.has_comments? + rescue => e + errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end end end - - true - rescue ActiveRecord::RecordInvalid => e - raise Projects::ImportService::Error, e.message end def import_pull_requests @@ -77,14 +99,12 @@ module Gitlab apply_labels(merge_request) import_comments(merge_request) import_comments_on_diff(merge_request) - rescue ActiveRecord::RecordInvalid => e - raise Projects::ImportService::Error, e.message + rescue => e + errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message } ensure clean_up_restored_branches(pull_request) end end - - true end def restore_source_branch(pull_request) @@ -98,7 +118,7 @@ module Gitlab def remove_branch(name) project.repository.delete_branch(name) rescue Rugged::ReferenceError - nil + errors << { type: :remove_branch, name: name } end def clean_up_restored_branches(pull_request) @@ -112,9 +132,10 @@ module Gitlab issue = client.issue(repo, issuable.iid) if issue.labels.count > 0 - label_ids = issue.labels.map do |raw| - Label.find_by(LabelFormatter.new(project, raw).attributes).try(:id) - end + label_ids = issue.labels + .map { |raw| LabelFormatter.new(project, raw).attributes } + .map { |attrs| Label.find_by(attrs).try(:id) } + .compact issuable.update_attribute(:label_ids, label_ids) end @@ -132,8 +153,12 @@ module Gitlab def create_comments(issuable, comments) comments.each do |raw| - comment = CommentFormatter.new(project, raw) - issuable.notes.create!(comment.attributes) + begin + comment = CommentFormatter.new(project, raw) + issuable.notes.create!(comment.attributes) + rescue => e + errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end end end @@ -143,16 +168,12 @@ module Gitlab gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url) project.update_attribute(:wiki_enabled, true) end - - true rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, # this means that repo has wiki enabled, but have no pages. So, # we can skip the import. if e.message !~ /repository not exported/ - raise Projects::ImportService::Error, e.message - else - true + errors << { type: :wiki, errors: e.message } end end end diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index b84538a090a268a0e97af332dd2528579941ecb1..04aa3664f640388dfa0de539512496f60048f594 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -56,6 +56,10 @@ module Gitlab end end + def url + raw_data.url + end + private def assigned? diff --git a/lib/gitlab/gitorious_import.rb b/lib/gitlab/gitorious_import.rb deleted file mode 100644 index 8d0132a744cac5ca1848541c6c01404e6125b0d7..0000000000000000000000000000000000000000 --- a/lib/gitlab/gitorious_import.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Gitlab - module GitoriousImport - GITORIOUS_HOST = "https://gitorious.org" - end -end diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb deleted file mode 100644 index 99fe5bdebfcf1a4cdf26b59373ee9a606bc62c19..0000000000000000000000000000000000000000 --- a/lib/gitlab/gitorious_import/client.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Gitlab - module GitoriousImport - class Client - attr_reader :repo_list - - def initialize(repo_list) - @repo_list = repo_list - end - - def authorize_url(redirect_uri) - "#{GITORIOUS_HOST}/gitlab-import?callback_url=#{redirect_uri}" - end - - def repos - @repos ||= repo_names.map { |full_name| GitoriousImport::Repository.new(full_name) } - end - - def repo(id) - repos.find { |repo| repo.id == id } - end - - private - - def repo_names - repo_list.to_s.split(',').map(&:strip).reject(&:blank?) - end - end - end -end diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb deleted file mode 100644 index 8e22aa9286ddf4d2a332f2daca87a6ab5f2eee9d..0000000000000000000000000000000000000000 --- a/lib/gitlab/gitorious_import/project_creator.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Gitlab - module GitoriousImport - class ProjectCreator - attr_reader :repo, :namespace, :current_user - - def initialize(repo, namespace, current_user) - @repo = repo - @namespace = namespace - @current_user = current_user - end - - def execute - ::Projects::CreateService.new( - current_user, - name: repo.name, - path: repo.path, - description: repo.description, - namespace_id: namespace.id, - visibility_level: Gitlab::VisibilityLevel::PUBLIC, - import_type: "gitorious", - import_source: repo.full_name, - import_url: repo.import_url - ).execute - end - end - end -end diff --git a/lib/gitlab/gitorious_import/repository.rb b/lib/gitlab/gitorious_import/repository.rb deleted file mode 100644 index c88f1ae358d1ce61b9ea95c84707577ad1c458ac..0000000000000000000000000000000000000000 --- a/lib/gitlab/gitorious_import/repository.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Gitlab - module GitoriousImport - Repository = Struct.new(:full_name) do - def id - Digest::SHA1.hexdigest(full_name) - end - - def namespace - segments.first - end - - def path - segments.last - end - - def name - path.titleize - end - - def description - "" - end - - def import_url - "#{GITORIOUS_HOST}/#{full_name}.git" - end - - private - - def segments - full_name.split('/') - end - end - end -end diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 59a05411fe9dc36395087ced47fb0ecc3e9bfd59..94261b7eeeded0b6e1760473f647c6e3d899a1bd 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -14,13 +14,12 @@ module Gitlab def options { - 'GitHub' => 'github', - 'Bitbucket' => 'bitbucket', - 'GitLab.com' => 'gitlab', - 'Gitorious.org' => 'gitorious', - 'Google Code' => 'google_code', - 'FogBugz' => 'fogbugz', - 'Repo by URL' => 'git', + 'GitHub' => 'github', + 'Bitbucket' => 'bitbucket', + 'GitLab.com' => 'gitlab', + 'Google Code' => 'google_code', + 'FogBugz' => 'fogbugz', + 'Repo by URL' => 'git', 'GitLab export' => 'gitlab_project' } end diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb index b4493bf44d28eacde9d5828ab30db6598aa48ed7..01c96a6fe960f223a7fce9b836291a83d39fd328 100644 --- a/lib/gitlab/metrics/rack_middleware.rb +++ b/lib/gitlab/metrics/rack_middleware.rb @@ -4,6 +4,17 @@ module Gitlab class RackMiddleware CONTROLLER_KEY = 'action_controller.instance' ENDPOINT_KEY = 'api.endpoint' + CONTENT_TYPES = { + 'text/html' => :html, + 'text/plain' => :txt, + 'application/json' => :json, + 'text/js' => :js, + 'application/atom+xml' => :atom, + 'image/png' => :png, + 'image/jpeg' => :jpeg, + 'image/gif' => :gif, + 'image/svg+xml' => :svg + } def initialize(app) @app = app @@ -46,8 +57,15 @@ module Gitlab end def tag_controller(trans, env) - controller = env[CONTROLLER_KEY] - trans.action = "#{controller.class.name}##{controller.action_name}" + controller = env[CONTROLLER_KEY] + action = "#{controller.class.name}##{controller.action_name}" + suffix = CONTENT_TYPES[controller.content_type] + + if suffix && suffix != :html + action += ".#{suffix}" + end + + trans.action = action end def tag_endpoint(trans, env) diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index fe65c246101e99c4aeac07f576a36f85c6491168..99d0c28e7493911e9c8789b43599a75938b34ca0 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -22,6 +22,8 @@ module Gitlab note_url when WikiPage wiki_page_url + when ProjectSnippet + project_snippet_url(object) else raise NotImplementedError.new("No URL builder defined for #{object.class}") end diff --git a/spec/controllers/import/gitorious_controller_spec.rb b/spec/controllers/import/gitorious_controller_spec.rb deleted file mode 100644 index 4ae2b78e11cb1e6befedba1f0b908dd49b835f29..0000000000000000000000000000000000000000 --- a/spec/controllers/import/gitorious_controller_spec.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'spec_helper' - -describe Import::GitoriousController do - include ImportSpecHelper - - let(:user) { create(:user) } - - before do - sign_in(user) - end - - describe "GET new" do - it "redirects to import endpoint on gitorious.org" do - get :new - - expect(controller).to redirect_to("https://gitorious.org/gitlab-import?callback_url=http://test.host/import/gitorious/callback") - end - end - - describe "GET callback" do - it "stores repo list in session" do - get :callback, repos: 'foo/bar,baz/qux' - - expect(session[:gitorious_repos]).to eq('foo/bar,baz/qux') - end - end - - describe "GET status" do - before do - @repo = OpenStruct.new(full_name: 'asd/vim') - end - - it "assigns variables" do - @project = create(:project, import_type: 'gitorious', creator_id: user.id) - stub_client(repos: [@repo]) - - get :status - - expect(assigns(:already_added_projects)).to eq([@project]) - expect(assigns(:repos)).to eq([@repo]) - end - - it "does not show already added project" do - @project = create(:project, import_type: 'gitorious', creator_id: user.id, import_source: 'asd/vim') - stub_client(repos: [@repo]) - - get :status - - expect(assigns(:already_added_projects)).to eq([@project]) - expect(assigns(:repos)).to eq([]) - end - end - - describe "POST create" do - before do - @repo = Gitlab::GitoriousImport::Repository.new('asd/vim') - end - - it "takes already existing namespace" do - namespace = create(:namespace, name: "asd", owner: user) - expect(Gitlab::GitoriousImport::ProjectCreator). - to receive(:new).with(@repo, namespace, user). - and_return(double(execute: true)) - stub_client(repo: @repo) - - post :create, format: :js - end - end -end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 0836b71056c7a7d0e44266b3dbb61ba991e494f0..16929767ddf7250f6f70e79d6ef4edd187357d38 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -8,13 +8,13 @@ describe Projects::IssuesController do describe "GET #index" do context 'external issue tracker' do it 'redirects to the external issue tracker' do - external = double(issues_url: 'https://example.com/issues') + external = double(project_path: 'https://example.com/project') allow(project).to receive(:external_issue_tracker).and_return(external) controller.instance_variable_set(:@project, project) get :index, namespace_id: project.namespace.path, project_id: project - expect(response).to redirect_to('https://example.com/issues') + expect(response).to redirect_to('https://example.com/project') end end diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index 4fd51a2349060aa4ccc53f96720dbc66deba525e..424ecc65759c563bf8562470c9f18e617167fcb4 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -14,6 +14,7 @@ FactoryGirl.define do note_events true build_events true pipeline_events true + wiki_page_events true end end end diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb index f4e5c26b51918579b76041fd35ae40d7f8d973e6..1df972843e2e9c3e678b943b48ec1f006d2bc98c 100644 --- a/spec/features/admin/admin_system_info_spec.rb +++ b/spec/features/admin/admin_system_info_spec.rb @@ -6,12 +6,49 @@ describe 'Admin System Info' do end describe 'GET /admin/system_info' do - it 'shows system info page' do - visit admin_system_info_path + let(:cpu) { double(:cpu, length: 2) } + let(:memory) { double(:memory, active_bytes: 4294967296, total_bytes: 17179869184) } - expect(page).to have_content 'CPU' - expect(page).to have_content 'Memory' - expect(page).to have_content 'Disks' + context 'when all info is available' do + before do + allow(Vmstat).to receive(:cpu).and_return(cpu) + allow(Vmstat).to receive(:memory).and_return(memory) + visit admin_system_info_path + end + + it 'shows system info page' do + expect(page).to have_content 'CPU 2 cores' + expect(page).to have_content 'Memory 4 GB / 16 GB' + expect(page).to have_content 'Disks' + end + end + + context 'when CPU info is not available' do + before do + allow(Vmstat).to receive(:cpu).and_raise(Errno::ENOENT) + allow(Vmstat).to receive(:memory).and_return(memory) + visit admin_system_info_path + end + + it 'shows system info page with no CPU info' do + expect(page).to have_content 'CPU Unable to collect CPU info' + expect(page).to have_content 'Memory 4 GB / 16 GB' + expect(page).to have_content 'Disks' + end + end + + context 'when memory info is not available' do + before do + allow(Vmstat).to receive(:cpu).and_return(cpu) + allow(Vmstat).to receive(:memory).and_raise(Errno::ENOENT) + visit admin_system_info_path + end + + it 'shows system info page with no CPU info' do + expect(page).to have_content 'CPU 2 cores' + expect(page).to have_content 'Memory Unable to collect memory info' + expect(page).to have_content 'Disks' + end end end end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index e528aff4d41454920daab187a0c44d1bdf8e98b5..fb0c47042857b1184fb362b7ed3ae01fd3f470c0 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -20,7 +20,7 @@ feature 'Start new branch from an issue', feature: true do context "when there is a referenced merge request" do let(:note) do create(:note, :on_issue, :system, project: project, - note: "mentioned in !#{referenced_mr.iid}") + note: "Mentioned in !#{referenced_mr.iid}") end let(:referenced_mr) do create(:merge_request, :simple, source_project: project, target_project: project, diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 930c36ade2bf48ef77e589545c477a70f24be605..759edf8ec80c5bb968604701df27ce9e3a987504 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -43,7 +43,8 @@ feature 'Merge request conflict resolution', js: true, feature: true do 'conflict-too-large' => 'when the conflicts contain a large file', 'conflict-binary-file' => 'when the conflicts contain a binary file', 'conflict-contains-conflict-markers' => 'when the conflicts contain a file with ambiguous conflict markers', - 'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another' + 'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another', + 'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file', } UNRESOLVABLE_CONFLICTS.each do |source_branch, description| diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..577c910f11b1fd186074b6f0fe6b8282163c98f8 --- /dev/null +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +feature 'Merge Request versions', js: true, feature: true do + before do + login_as :admin + merge_request = create(:merge_request, importing: true) + merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') + project = merge_request.source_project + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'show the latest version of the diff' do + page.within '.mr-version-switch' do + expect(page).to have_content 'Version: latest' + end + + expect(page).to have_content '8 changed files' + end + + describe 'switch between versions' do + before do + page.within '.mr-version-switch' do + find('.btn-link').click + click_link '6f6d7e7e' + end + end + + it 'should show older version' do + page.within '.mr-version-switch' do + expect(page).to have_content 'Version: 6f6d7e7e' + end + + expect(page).to have_content '5 changed files' + end + end +end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 7a9edbbe33968fda188cd4f9772f80b9013181c2..f1c522155d36d254ee98af97c12d94fad53866c4 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -141,7 +141,7 @@ describe 'Comments', feature: true do let(:project2) { create(:project, :private) } let(:issue) { create(:issue, project: project2) } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') } - let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") } + let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "Mentioned in #{issue.to_reference(project)}") } it 'shows the system note' do login_as :admin diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 0342f4f1d97e31b9dcde976092e1af23354cf940..32544f3f53802b7dbd769a241ea2e19530981257 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -41,6 +41,27 @@ describe 'Dashboard Todos', feature: true do expect(page).to have_content("You're all done!") end end + + context 'todo is stale on the page' do + before do + todos = TodosFinder.new(user, state: :pending).execute + TodoService.new.mark_todos_as_done(todos, user) + end + + describe 'deleting the todo' do + before do + first('.done-todo').click + end + + it 'is removed from the list' do + expect(page).not_to have_selector('.todos-list .todo') + end + + it 'shows "All done" message' do + expect(page).to have_content("You're all done!") + end + end + end end context 'User has Todos with labels spanning multiple projects' do diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 604204cca0a4c3a6ce760694bd4101d50990fde0..284b58d8d5cad0ee1ecc1c919ba022c7e6071659 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -136,4 +136,42 @@ describe ProjectsHelper do expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') end end + + describe '#last_push_event' do + let(:user) { double(:user, fork_of: nil) } + let(:project) { double(:project, id: 1) } + + before do + allow(helper).to receive(:current_user).and_return(user) + helper.instance_variable_set(:@project, project) + end + + context 'when there is no current_user' do + let(:user) { nil } + + it 'returns nil' do + expect(helper.last_push_event).to eq(nil) + end + end + + it 'returns recent push on the current project' do + event = double(:event) + expect(user).to receive(:recent_push).with([project.id]).and_return(event) + + expect(helper.last_push_event).to eq(event) + end + + context 'when current user has a fork of the current project' do + let(:fork) { double(:fork, id: 2) } + + it 'returns recent push considering fork events' do + expect(user).to receive(:fork_of).with(project).and_return(fork) + + event_on_fork = double(:event) + expect(user).to receive(:recent_push).with([project.id, fork.id]).and_return(event_on_fork) + + expect(helper.last_push_event).to eq(event_on_fork) + end + end + end end diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index fa32d0d7da5760f284e27bce7c2e334729102fcb..c1c12b57b532bbc91f3865ab002bcbbe6aa3e6a1 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -11,7 +11,7 @@ /*= require ./fixtures/emoji_menu */ (function() { - var awardsHandler, lazyAssert; + var awardsHandler, lazyAssert, urlRoot; awardsHandler = null; @@ -27,6 +27,7 @@ }; gon.award_menu_url = '/emojis'; + urlRoot = gon.relative_url_root; lazyAssert = function(done, assertFn) { return setTimeout(function() { @@ -45,9 +46,14 @@ return cb(); }; })(this)); - return spyOn(jQuery, 'get').and.callFake(function(req, cb) { + spyOn(jQuery, 'get').and.callFake(function(req, cb) { return cb(window.emojiMenu); }); + spyOn(jQuery, 'cookie'); + }); + afterEach(function() { + // restore original url root value + gon.relative_url_root = urlRoot; }); describe('::showEmojiMenu', function() { it('should show emoji menu when Add emoji button clicked', function(done) { @@ -189,6 +195,28 @@ return expect($thumbsUpEmoji.data("original-title")).toBe('sam'); }); }); + describe('::addEmojiToFrequentlyUsedList', function() { + it('should set a cookie with the correct default path', function() { + gon.relative_url_root = ''; + awardsHandler.addEmojiToFrequentlyUsedList('sunglasses'); + expect(jQuery.cookie) + .toHaveBeenCalledWith('frequently_used_emojis', 'sunglasses', { + path: '/', + expires: 365 + }) + ; + }); + it('should set a cookie with the correct custom root path', function() { + gon.relative_url_root = '/gitlab/subdir'; + awardsHandler.addEmojiToFrequentlyUsedList('alien'); + expect(jQuery.cookie) + .toHaveBeenCalledWith('frequently_used_emojis', 'alien', { + path: '/gitlab/subdir', + expires: 365 + }) + ; + }); + }); describe('search', function() { return it('should filter the emoji', function() { $('.js-add-award').eq(0).click(); diff --git a/spec/javascripts/datetime_utility_spec.js.coffee b/spec/javascripts/datetime_utility_spec.js.coffee index 6b9617341fe7b34dc86fe374ebfa2ccb9bea4ee6..8bd113e7d860ff679d7deeaa6b4527779ab6e001 100644 --- a/spec/javascripts/datetime_utility_spec.js.coffee +++ b/spec/javascripts/datetime_utility_spec.js.coffee @@ -29,3 +29,22 @@ describe 'Date time utils', -> it 'should return Saturday', -> day = gl.utils.getDayName(new Date('07/23/2016')) expect(day).toBe('Saturday') + + describe 'get day difference', -> + it 'should return 7', -> + firstDay = new Date('07/01/2016') + secondDay = new Date('07/08/2016') + difference = gl.utils.getDayDifference(firstDay, secondDay) + expect(difference).toBe(7) + + it 'should return 31', -> + firstDay = new Date('07/01/2016') + secondDay = new Date('08/01/2016') + difference = gl.utils.getDayDifference(firstDay, secondDay) + expect(difference).toBe(31) + + it 'should return 365', -> + firstDay = new Date('07/02/2015') + secondDay = new Date('07/01/2016') + difference = gl.utils.getDayDifference(firstDay, secondDay) + expect(difference).toBe(365) \ No newline at end of file diff --git a/spec/javascripts/fixtures/awards_handler.html.haml b/spec/javascripts/fixtures/awards_handler.html.haml index d55936ee4f9e61f737c735b8e4212a92288e519e..1ef2e8f862496436a5a60d1cfabd73fd7535093d 100644 --- a/spec/javascripts/fixtures/awards_handler.html.haml +++ b/spec/javascripts/fixtures/awards_handler.html.haml @@ -39,7 +39,7 @@ %span.note-role Reporter %a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"} %i.fa.fa-spinner.fa-spin - %i.fa.fa-smile-o + %i.fa.fa-smile-o.link-highlight .js-task-list-container.note-body.is-task-list-enabled .note-text %p Suscipit sunt quia quisquam sed eveniet ipsam. diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json index 84e8d0ba1e4b3d87e825375c6dd32ffd67222a29..4919d77e5a46fa1a2b90b0753777694f8fe4cb65 100644 --- a/spec/javascripts/fixtures/projects.json +++ b/spec/javascripts/fixtures/projects.json @@ -1 +1 @@ -[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}] +[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":true,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":true,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}] diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 86d04ecfa36b0777b2ec5da99ecabdf53ba7381e..e10c1f5c5474451543b93a5d0b671cbfaf4f854c 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -51,6 +51,16 @@ describe ExtractsPath, lib: true do expect(@path).to eq(params[:path]) end end + + context 'subclass overrides get_id' do + it 'uses ref returned by get_id' do + allow_any_instance_of(self.class).to receive(:get_id){ '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } + + assign_ref_vars + + expect(@id).to eq(get_id) + end + end end describe '#extract_ref' do diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb index 65a828accdec47c9d32567c9a2a1dcab8a8ad4d1..a1d2ca1e27263080b4c883818e36447e1993c191 100644 --- a/spec/lib/gitlab/conflict/parser_spec.rb +++ b/spec/lib/gitlab/conflict/parser_spec.rb @@ -183,6 +183,11 @@ CONFLICT expect { parse_text('a' * 102401) }. to raise_error(Gitlab::Conflict::Parser::UnmergeableFile) end + + it 'raises UnsupportedEncoding when the file contains non-UTF-8 characters' do + expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }. + to raise_error(Gitlab::Conflict::Parser::UnsupportedEncoding) + end end end end diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b7c3bc4e1a74983c75c024262d4659597d61d1cf --- /dev/null +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +describe Gitlab::GithubImport::Importer, lib: true do + describe '#execute' do + context 'when an error occurs' do + let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_enabled: false) } + let(:octocat) { double(id: 123456, login: 'octocat') } + let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } + let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } + let(:repository) { double(id: 1, fork: false) } + let(:source_sha) { create(:commit, project: project).id } + let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) } + let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } + let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) } + + let(:label) do + double( + name: 'Bug', + color: 'ff0000', + url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug' + ) + end + + let(:milestone) do + double( + number: 1347, + state: 'open', + title: '1.0', + description: 'Version 1.0', + due_on: nil, + created_at: created_at, + updated_at: updated_at, + closed_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/milestones/1' + ) + end + + let(:issue1) do + double( + number: 1347, + milestone: nil, + state: 'open', + title: 'Found a bug', + body: "I'm having a problem with this.", + assignee: nil, + user: octocat, + comments: 0, + pull_request: nil, + created_at: created_at, + updated_at: updated_at, + closed_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347' + ) + end + + let(:issue2) do + double( + number: 1348, + milestone: nil, + state: 'open', + title: nil, + body: "I'm having a problem with this.", + assignee: nil, + user: octocat, + comments: 0, + pull_request: nil, + created_at: created_at, + updated_at: updated_at, + closed_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348' + ) + end + + let(:pull_request) do + double( + number: 1347, + milestone: nil, + state: 'open', + title: 'New feature', + body: 'Please pull these awesome changes', + head: source_branch, + base: target_branch, + assignee: nil, + user: octocat, + created_at: created_at, + updated_at: updated_at, + closed_at: nil, + merged_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347' + ) + end + + before do + allow(project).to receive(:import_data).and_return(double.as_null_object) + allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound) + allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label, label]) + allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone]) + allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2]) + allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request]) + allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil })) + allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error) + end + + it 'returns true' do + expect(described_class.new(project).execute).to eq true + end + + it 'does not raise an error' do + expect { described_class.new(project).execute }.not_to raise_error + end + + it 'stores error messages' do + error = { + message: 'The remote data could not be fully imported.', + errors: [ + { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title has already been taken" }, + { type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" }, + { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." }, + { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" }, + { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." }, + { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" }, + { type: :wiki, errors: "Gitlab::Shell::Error" } + ] + } + + described_class.new(project).execute + + expect(project.import_error).to eq error.to_json + end + end + end +end diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index aa28e360993434ba264a3362a65a4d5f5ac07e75..b667abf063d65e088307602081637dd8a7c13878 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -27,7 +27,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do created_at: created_at, updated_at: updated_at, closed_at: nil, - merged_at: nil + merged_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347' } end @@ -229,4 +230,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do end end end + + describe '#url' do + let(:raw_data) { double(base_data) } + + it 'return raw url' do + expect(pull_request.url).to eq 'https://api.github.com/repos/octocat/Hello-World/pulls/1347' + end + end end diff --git a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb deleted file mode 100644 index 946712ca38eb9dfdcacfc3f1382f8e63afe3a751..0000000000000000000000000000000000000000 --- a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'spec_helper' - -describe Gitlab::GitoriousImport::ProjectCreator, lib: true do - let(:user) { create(:user) } - let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') } - let(:namespace){ create(:group, owner: user) } - - before do - namespace.add_owner(user) - end - - it 'creates project' do - allow_any_instance_of(Project).to receive(:add_import_job) - - project_creator = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, user) - project = project_creator.execute - - expect(project.name).to eq("Bar Baz Qux") - expect(project.path).to eq("bar-baz-qux") - expect(project.namespace).to eq(namespace) - expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) - expect(project.import_type).to eq("gitorious") - expect(project.import_source).to eq("foo/bar-baz-qux") - expect(project.import_url).to eq("https://gitorious.org/foo/bar-baz-qux.git") - end -end diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb index a30cb2a5e38220c41e8683062781542507cd15de..bcaffd279090309ca3a141327e3f5bade87af6ce 100644 --- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Metrics::RackMiddleware do end it 'tags a transaction with the name and action of a controller' do - klass = double(:klass, name: 'TestController') + klass = double(:klass, name: 'TestController', content_type: 'text/html') controller = double(:controller, class: klass, action_name: 'show') env['action_controller.instance'] = controller @@ -32,7 +32,7 @@ describe Gitlab::Metrics::RackMiddleware do middleware.call(env) end - it 'tags a transaction with the method andpath of the route in the grape endpoint' do + it 'tags a transaction with the method and path of the route in the grape endpoint' do route = double(:route, route_method: "GET", route_path: "/:version/projects/:id/archive(.:format)") endpoint = double(:endpoint, route: route) @@ -87,17 +87,30 @@ describe Gitlab::Metrics::RackMiddleware do describe '#tag_controller' do let(:transaction) { middleware.transaction_from_env(env) } + let(:content_type) { 'text/html' } - it 'tags a transaction with the name and action of a controller' do + before do klass = double(:klass, name: 'TestController') - controller = double(:controller, class: klass, action_name: 'show') + controller = double(:controller, class: klass, action_name: 'show', content_type: content_type) env['action_controller.instance'] = controller + end + it 'tags a transaction with the name and action of a controller' do middleware.tag_controller(transaction, env) expect(transaction.action).to eq('TestController#show') end + + context 'when the response content type is not :html' do + let(:content_type) { 'application/json' } + + it 'appends the mime type to the transaction action' do + middleware.tag_controller(transaction, env) + + expect(transaction.action).to eq('TestController#show.json') + end + end end describe '#tag_endpoint' do diff --git a/spec/models/concerns/statuseable_spec.rb b/spec/models/concerns/has_status_spec.rb similarity index 97% rename from spec/models/concerns/statuseable_spec.rb rename to spec/models/concerns/has_status_spec.rb index 8e0a2a2cbdea9ab38280e8e439d64ce8661f7c2b..e118432d0987835800a87ccf6a3ea1d720ae6638 100644 --- a/spec/models/concerns/statuseable_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe Statuseable do +describe HasStatus do before do @object = Object.new - @object.extend(Statuseable::ClassMethods) + @object.extend(HasStatus::ClassMethods) end describe '.status' do @@ -12,7 +12,7 @@ describe Statuseable do end subject { @object.status } - + shared_examples 'build status summary' do context 'all successful' do let(:statuses) { Array.new(2) { create(type, status: :success) } } diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 29f7396f862822fcbaf7647353e8be12aacc9e5b..e5b185dc3f642a0a11a95e75e7ddb6a8cc7a1b74 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -1,6 +1,27 @@ require 'spec_helper' describe MergeRequestDiff, models: true do + describe 'create new record' do + subject { create(:merge_request).merge_request_diff } + + it { expect(subject).to be_valid } + it { expect(subject).to be_persisted } + it { expect(subject.commits.count).to eq(5) } + it { expect(subject.diffs.count).to eq(8) } + it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } + it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') } + end + + describe '#latest' do + let!(:mr) { create(:merge_request, :with_diffs) } + let!(:first_diff) { mr.merge_request_diff } + let!(:last_diff) { mr.create_merge_request_diff } + + it { expect(last_diff.latest?).to be_truthy } + it { expect(first_diff.latest?).to be_falsey } + end + describe '#diffs' do let(:mr) { create(:merge_request, :with_diffs) } let(:mr_diff) { mr.merge_request_diff } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 64c56d922fff11468bffa82acbd06920041b107e..d67f71bbb9c2dc6320311eabae482d8b3ef6cd67 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -9,7 +9,7 @@ describe MergeRequest, models: true do it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } it { is_expected.to belong_to(:merge_user).class_name("User") } - it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) } + it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) } end describe 'modules' do @@ -159,7 +159,7 @@ describe MergeRequest, models: true do context 'when there are MR diffs' do it 'delegates to the MR diffs' do - merge_request.merge_request_diff = MergeRequestDiff.new + merge_request.save expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options)) @@ -316,7 +316,7 @@ describe MergeRequest, models: true do end it "can be removed if the last commit is the head of the source branch" do - allow(subject.source_project).to receive(:commit).and_return(subject.diff_head_commit) + allow(subject).to receive(:source_branch_head).and_return(subject.diff_head_commit) expect(subject.can_remove_source_branch?(user)).to be_truthy end @@ -721,12 +721,15 @@ describe MergeRequest, models: true do let(:commit) { subject.project.commit(sample_commit.id) } - it "reloads the diff content" do - expect(subject.merge_request_diff).to receive(:reload_content) - + it "does not change existing merge request diff" do + expect(subject.merge_request_diff).not_to receive(:save_git_content) subject.reload_diff end + it "creates new merge request diff" do + expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1) + end + it "executs diff cache service" do expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject) @@ -736,13 +739,15 @@ describe MergeRequest, models: true do it "updates diff note positions" do old_diff_refs = subject.diff_refs - merge_request_diff = subject.merge_request_diff - # Update merge_request_diff so that #diff_refs will return commit.diff_refs - allow(merge_request_diff).to receive(:reload_content) do - merge_request_diff.base_commit_sha = commit.parent_id - merge_request_diff.start_commit_sha = commit.parent_id - merge_request_diff.head_commit_sha = commit.sha + allow(subject).to receive(:create_merge_request_diff) do + subject.merge_request_diffs.create( + base_commit_sha: commit.parent_id, + start_commit_sha: commit.parent_id, + head_commit_sha: commit.sha + ) + + subject.merge_request_diff(true) end expect(Notes::DiffPositionUpdateService).to receive(:new).with( @@ -752,14 +757,31 @@ describe MergeRequest, models: true do new_diff_refs: commit.diff_refs, paths: note.position.paths ).and_call_original - expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note) + expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note) expect_any_instance_of(DiffNote).to receive(:save).once subject.reload_diff end end + describe '#branch_merge_base_commit' do + context 'source and target branch exist' do + it { expect(subject.branch_merge_base_commit.sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } + it { expect(subject.branch_merge_base_commit).to be_a(Commit) } + end + + context 'when the target branch does not exist' do + before do + subject.project.repository.raw_repository.delete_branch(subject.target_branch) + end + + it 'returns nil' do + expect(subject.branch_merge_base_commit).to be_nil + end + end + end + describe "#diff_sha_refs" do context "with diffs" do subject { create(:merge_request, :with_diffs) } @@ -890,6 +912,19 @@ describe MergeRequest, models: true do expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey end + it 'returns a falsey value when the MR is marked as having conflicts, but has none' do + merge_request = create_merge_request('master') + + expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey + end + + it 'returns a falsey value when the MR has a missing ref after a force push' do + merge_request = create_merge_request('conflict-resolvable') + allow(merge_request.conflicts).to receive(:merge_index).and_raise(Rugged::OdbError) + + expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey + end + it 'returns a falsey value when the MR does not support new diff notes' do merge_request = create_merge_request('conflict-resolvable') merge_request.merge_request_diff.update_attributes(start_commit_sha: nil) diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index ef2747046b96131c35e9f8fd9d402f5417cd70b0..9e8ae07e0b2e108a923b19f08558973bb9997442 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -225,7 +225,7 @@ describe Note, models: true do let(:note) do create :note, noteable: ext_issue, project: ext_proj, - note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9a3660012f995320f35f3ff3f657d7cbcf20786d..b2baeeb31bbfce9d109accf5c78f409b1d65f033 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1442,4 +1442,35 @@ describe Project, models: true do expect(shared_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true) end end + + describe 'change_head' do + let(:project) { create(:project) } + + it 'calls the before_change_head method' do + expect(project.repository).to receive(:before_change_head) + project.change_head(project.default_branch) + end + + it 'creates the new reference with rugged' do + expect(project.repository.rugged.references).to receive(:create).with('HEAD', + "refs/heads/#{project.default_branch}", + force: true) + project.change_head(project.default_branch) + end + + it 'copies the gitattributes' do + expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch) + project.change_head(project.default_branch) + end + + it 'expires the avatar cache' do + expect(project.repository).to receive(:expire_avatar_cache).with(project.default_branch) + project.change_head(project.default_branch) + end + + it 'reloads the default branch' do + expect(project).to receive(:reload_default_branch) + project.change_head(project.default_branch) + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 51e4780e2b1035b1bd96fee6ddb941a178885bf5..8eb0c5033c977a08f78fec0c178708a15d6b7541 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -920,6 +920,16 @@ describe User, models: true do expect(subject.recent_push).to eq(nil) end + + it "includes push events on any of the provided projects" do + expect(subject.recent_push(project1)).to eq(nil) + expect(subject.recent_push(project2)).to eq(push_event) + + push_data1 = Gitlab::DataBuilder::Push.build_sample(project1, subject) + push_event1 = create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject, data: push_data1) + + expect(subject.recent_push([project1, project2])).to eq(push_event1) # Newest + end end describe '#authorized_groups' do diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb index c65510fadecf0dc1aafabb76ac52ce064c58f4f7..bbdf8f03c2bc3578aec1c547d57853677ca2ed55 100644 --- a/spec/requests/api/api_helpers_spec.rb +++ b/spec/requests/api/api_helpers_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe API::Helpers, api: true do include API::Helpers include ApiHelpers + include SentryHelper let(:user) { create(:user) } let(:admin) { create(:admin) } @@ -234,4 +235,30 @@ describe API::Helpers, api: true do expect(to_boolean(nil)).to be_nil end end + + describe '.handle_api_exception' do + before do + allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true) + allow_any_instance_of(self.class).to receive(:rack_response) + end + + it 'does not report a MethodNotAllowed exception to Sentry' do + exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' }) + allow(exception).to receive(:backtrace).and_return(caller) + + expect(Raven).not_to receive(:capture_exception).with(exception) + + handle_api_exception(exception) + end + + it 'does report RuntimeError to Sentry' do + exception = RuntimeError.new('test error') + allow(exception).to receive(:backtrace).and_return(caller) + + expect_any_instance_of(self.class).to receive(:sentry_context) + expect(Raven).to receive(:capture_exception).with(exception) + + handle_api_exception(exception) + end + end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index be52f88831f7078b468495bba8038632e4602349..5d06abcfeb37e00db424e7c63948643d21b0ca2b 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -38,6 +38,68 @@ describe API::API, api: true do end end + describe 'GET /internal/two_factor_recovery_codes' do + it 'returns an error message when the key does not exist' do + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: 12345 + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Not found') + end + + it 'returns an error message when the key is a deploy key' do + deploy_key = create(:deploy_key) + + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: deploy_key.id + + expect(json_response['success']).to be_falsey + expect(json_response['message']).to eq('Deploy keys cannot be used to retrieve recovery codes') + end + + it 'returns an error message when the user does not exist' do + key_without_user = create(:key, user: nil) + + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: key_without_user.id + + expect(json_response['success']).to be_falsey + expect(json_response['message']).to eq('Could not find a user for the given key') + expect(json_response['recovery_codes']).to be_nil + end + + context 'when two-factor is enabled' do + it 'returns new recovery codes when the user exists' do + allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(true) + allow_any_instance_of(User) + .to receive(:generate_otp_backup_codes!).and_return(%w(119135e5a3ebce8e 34bd7b74adbc8861)) + + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: key.id + + expect(json_response['success']).to be_truthy + expect(json_response['recovery_codes']).to match_array(%w(119135e5a3ebce8e 34bd7b74adbc8861)) + end + end + + context 'when two-factor is not enabled' do + it 'returns an error message' do + allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(false) + + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: key.id + + expect(json_response['success']).to be_falsey + expect(json_response['recovery_codes']).to be_nil + end + end + end + describe "GET /internal/discover" do it do get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index a40e1a93b715abf29ec2d04c85dbee6af0b25ae0..b8038fc85a19774feb57e5ae0bd7db5de60d5383 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -61,6 +61,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(issue.title) + expect(json_response.last).to have_key('web_url') end it "adds pagination headers and keep query params" do diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..8f1e5ac98917a5b630113c4ae3cafe1bb332d0fc --- /dev/null +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -0,0 +1,49 @@ +require "spec_helper" + +describe API::API, 'MergeRequestDiffs', api: true do + include ApiHelpers + + let!(:user) { create(:user) } + let!(:merge_request) { create(:merge_request, importing: true) } + let!(:project) { merge_request.target_project } + + before do + merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') + project.team << [user, :master] + end + + describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do + context 'valid merge request' do + before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) } + let(:merge_request_diff) { merge_request.merge_request_diffs.first } + + it { expect(response.status).to eq 200 } + it { expect(json_response.size).to eq(merge_request.merge_request_diffs.size) } + it { expect(json_response.first['id']).to eq(merge_request_diff.id) } + it { expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) } + end + + it 'returns a 404 when merge_request_id not found' do + get api("/projects/#{project.id}/merge_requests/999/versions", user) + expect(response).to have_http_status(404) + end + end + + describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do + context 'valid merge request' do + before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) } + let(:merge_request_diff) { merge_request.merge_request_diffs.first } + + it { expect(response.status).to eq 200 } + it { expect(json_response['id']).to eq(merge_request_diff.id) } + it { expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) } + it { expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size) } + end + + it 'returns a 404 when merge_request_id not found' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user) + expect(response).to have_http_status(404) + end + end +end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 617600d617344f2922e54c2fd393ec43010bef75..baff872e28e01be4583ebc6a820d52bc0e13a8a2 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -33,6 +33,7 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) + expect(json_response.last).to have_key('web_url') end it "returns an array of all merge_requests" do diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 737fa14cbb0c366a1ee1ad8b6fb5bc04f33fc2c1..223444ea39fc62cd519f152401bd0ba3a07de4a6 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -25,7 +25,7 @@ describe API::API, api: true do let!(:cross_reference_note) do create :note, noteable: ext_issue, project: ext_proj, - note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 914e88c94878d0cfa3b55b52f1496a51f2501a4b..765dc8a8f666d5d1ffdeb3db60abc65458dcd1ce 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -34,6 +34,7 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response.first['note_events']).to eq(true) expect(json_response.first['build_events']).to eq(true) expect(json_response.first['pipeline_events']).to eq(true) + expect(json_response.first['wiki_page_events']).to eq(true) expect(json_response.first['enable_ssl_verification']).to eq(true) end end @@ -57,6 +58,9 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events) expect(json_response['tag_push_events']).to eq(hook.tag_push_events) expect(json_response['note_events']).to eq(hook.note_events) + expect(json_response['build_events']).to eq(hook.build_events) + expect(json_response['pipeline_events']).to eq(hook.pipeline_events) + expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) end @@ -93,6 +97,7 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['note_events']).to eq(false) expect(json_response['build_events']).to eq(false) expect(json_response['pipeline_events']).to eq(false) + expect(json_response['wiki_page_events']).to eq(false) expect(json_response['enable_ssl_verification']).to eq(true) end @@ -118,6 +123,9 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events) expect(json_response['tag_push_events']).to eq(hook.tag_push_events) expect(json_response['note_events']).to eq(hook.note_events) + expect(json_response['build_events']).to eq(hook.build_events) + expect(json_response['pipeline_events']).to eq(hook.pipeline_events) + expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) end diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 42757ff21b081d984dc0486fff3c32cb8dad32b0..01148f0a05ea427dfca7d71a09bd0baeb5af2204 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -30,6 +30,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response.size).to eq(3) expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) + expect(json_response.last).to have_key('web_url') end it 'hides private snippets from regular user' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 4742b3d0e374e69e609318fbc0f7a80783073b88..63f2467be63e52e05e00374d5b113d6d2b2913d4 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -224,7 +224,8 @@ describe API::API, api: true do description: FFaker::Lorem.sentence, issues_enabled: false, merge_requests_enabled: false, - wiki_enabled: false + wiki_enabled: false, + only_allow_merge_if_build_succeeds: false }) post api('/projects', user), project @@ -276,6 +277,18 @@ describe API::API, api: true do expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) end + it 'sets a project as allowing merge even if build fails' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) + post api('/projects', user), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey + end + + it 'sets a project as allowing merge only if build succeeds' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) + post api('/projects', user), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy + end + context 'when a visibility level is restricted' do before do @project = attributes_for(:project, { public: true }) @@ -384,6 +397,18 @@ describe API::API, api: true do expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) end + + it 'sets a project as allowing merge even if build fails' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) + post api("/projects/user/#{user.id}", admin), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey + end + + it 'sets a project as allowing merge only if build succeeds' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) + post api("/projects/user/#{user.id}", admin), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy + end end describe "POST /projects/:id/uploads" do @@ -444,6 +469,7 @@ describe API::API, api: true do expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) + expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) end it 'returns a project by path name' do diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index f7f45983d26e6a4490fde59d8aedfcb637f01d63..cf4c5f13635aa810e0e4972865656147e4190bff 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -30,7 +30,7 @@ describe Boards::Issues::ListService, services: true do let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) } let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) } let!(:closed_issue3) { create(:issue, :closed, project: project) } - let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) } + let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1, development]) } before do project.team << [user, :developer] @@ -58,15 +58,15 @@ describe Boards::Issues::ListService, services: true do issues = described_class.new(project, user, params).execute - expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1] + expect(issues).to eq [closed_issue2, closed_issue3, closed_issue1] end - it 'returns opened issues that have label list applied when listing issues from a label list' do + it 'returns opened/closed issues that have label list applied when listing issues from a label list' do params = { id: list1.id } issues = described_class.new(project, user, params).execute - expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] + expect(issues).to eq [closed_issue4, list1_issue3, list1_issue1, list1_issue2] end end end diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb index c4b874682751c550ab3ce83e6d90b25ce9f15046..807f89e80b76736270ceda59138d48f47164bd11 100644 --- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb +++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb @@ -6,7 +6,7 @@ describe MergeRequests::MergeRequestDiffCacheService do describe '#execute' do it 'retrieves the diff files to cache the highlighted result' do merge_request = create(:merge_request) - cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequest.default_options] + cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequestDiff.default_options] expect(Rails.cache).to receive(:read).with(cache_key).and_return({}) expect(Rails.cache).to receive(:write).with(cache_key, anything) diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 00427d6db2a6c29dd1d7313df647940daf0e555c..3d854a959f309cad1010695e8b9e8850154b19a9 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -330,13 +330,13 @@ describe SystemNoteService, services: true do let(:mentioner) { project2.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}" + expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference(project)}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}" + expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference(project)}" end end end @@ -346,13 +346,13 @@ describe SystemNoteService, services: true do let(:mentioner) { project.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}" + expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}" + expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference}" end end end @@ -362,7 +362,7 @@ describe SystemNoteService, services: true do describe '.cross_reference?' do it 'is truthy when text begins with expected text' do - expect(described_class.cross_reference?('mentioned in something')).to be_truthy + expect(described_class.cross_reference?('Mentioned in something')).to be_truthy end it 'is falsey when text does not begin with expected text' do diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 296fd1bd5a4339b3d72511011d38dd39a5df8a6a..cafcad3e3c04a46aae0d189cd775a2f55c6e9ae2 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -496,6 +496,7 @@ describe TodoService, services: true do describe '#mark_todos_as_done' do let(:issue) { create(:issue, project: project, author: author, assignee: john_doe) } + let(:another_issue) { create(:issue, project: project, author: author, assignee: john_doe) } it 'marks a relation of todos as done' do create(:todo, :mentioned, user: john_doe, target: issue, project: project) @@ -518,6 +519,26 @@ describe TodoService, services: true do expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq(1) end + context 'when some of the todos are done already' do + before do + create(:todo, :mentioned, user: john_doe, target: issue, project: project) + create(:todo, :mentioned, user: john_doe, target: another_issue, project: project) + end + + it 'returns the number of those still pending' do + TodoService.new.mark_pending_todos_as_done(issue, john_doe) + + expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(1) + end + + it 'returns 0 if all are done' do + TodoService.new.mark_pending_todos_as_done(issue, john_doe) + TodoService.new.mark_pending_todos_as_done(another_issue, john_doe) + + expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(0) + end + end + it 'caches the number of todos of a user', :caching do create(:todo, :mentioned, user: john_doe, target: issue, project: project) todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index edbbfc3c9e5729674525103443a8a382c5262f9c..c7a45fc4ff9a62681bd9b548e5bfc70f3f9fafcf 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -24,11 +24,12 @@ module TestEnv 'expand-collapse-lines' => '238e82d', 'video' => '8879059', 'crlf-diff' => '5938907', - 'conflict-start' => '14fa46b', + 'conflict-start' => '75284c7', 'conflict-resolvable' => '1450cd6', 'conflict-binary-file' => '259a6fb', 'conflict-contains-conflict-markers' => '5e0964c', 'conflict-missing-side' => 'eb227b3', + 'conflict-non-utf8' => 'd0a293c', 'conflict-too-large' => '39fa04f', }