diff options
177 files changed, 2300 insertions, 808 deletions
diff --git a/.flayignore b/.flayignore index 9c9875d4f9e..f120de527bd 100644 --- a/.flayignore +++ b/.flayignore @@ -1 +1,2 @@ *.erb +lib/gitlab/sanitizers/svg/whitelist.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f51d506f64a..b167fc74996 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -206,10 +206,15 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21 - bundle exec $CI_BUILD_NAME rubocop: *exec +rake haml_lint: *exec rake scss_lint: *exec rake brakeman: *exec -rake flog: *exec -rake flay: *exec +rake flog: + <<: *exec + allow_failure: yes +rake flay: + <<: *exec + allow_failure: yes license_finder: *exec rake downtime_check: *exec diff --git a/.haml-lint.yml b/.haml-lint.yml new file mode 100644 index 00000000000..da9a43d9c6d --- /dev/null +++ b/.haml-lint.yml @@ -0,0 +1,103 @@ +# Whether to ignore frontmatter at the beginning of HAML documents for +# frameworks such as Jekyll/Middleman +skip_frontmatter: false +exclude: + - 'vendor/**/*' + - 'spec/**/*' + +linters: + AltText: + enabled: false + + ClassAttributeWithStaticValue: + enabled: false + + ClassesBeforeIds: + enabled: false + + ConsecutiveComments: + enabled: false + + ConsecutiveSilentScripts: + enabled: false + max_consecutive: 2 + + EmptyObjectReference: + enabled: true + + EmptyScript: + enabled: true + + FinalNewline: + enabled: false + present: true + + HtmlAttributes: + enabled: false + + ImplicitDiv: + enabled: false + + LeadingCommentSpace: + enabled: false + + LineLength: + enabled: false + max: 80 + + MultilinePipe: + enabled: false + + MultilineScript: + enabled: true + + ObjectReferenceAttributes: + enabled: true + + RuboCop: + enabled: false + # These cops are incredibly noisy when it comes to HAML templates, so we + # ignore them. + ignored_cops: + - Lint/BlockAlignment + - Lint/EndAlignment + - Lint/Void + - Metrics/LineLength + - Style/AlignParameters + - Style/BlockNesting + - Style/ElseAlignment + - Style/FileName + - Style/FinalNewline + - Style/FrozenStringLiteralComment + - Style/IfUnlessModifier + - Style/IndentationWidth + - Style/Next + - Style/TrailingBlankLines + - Style/TrailingWhitespace + - Style/WhileUntilModifier + + RubyComments: + enabled: false + + SpaceBeforeScript: + enabled: false + + SpaceInsideHashAttributes: + enabled: false + style: space + + Indentation: + enabled: true + character: space # or tab + + TagName: + enabled: true + + TrailingWhitespace: + enabled: false + + UnnecessaryInterpolation: + enabled: false + + UnnecessaryStringOutput: + enabled: false diff --git a/.rubocop.yml b/.rubocop.yml index 5bd31ccf329..b054675d677 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -767,26 +767,33 @@ Rails/ScopeArgs: RSpec/AnyInstance: Enabled: false -# Check that the first argument to the top level describe is the tested class or -# module. +# Check for expectations where `be(...)` can replace `eql(...)`. +RSpec/BeEql: + Enabled: false + +# Check that the first argument to the top level describe is a constant. RSpec/DescribeClass: Enabled: false -# Use `described_class` for tested class / module. +# Checks that tests use `described_class`. +RSpec/DescribedClass: + Enabled: false + +# Checks that the second argument to `describe` specifies a method. RSpec/DescribeMethod: Enabled: false -# Checks that the second argument to top level describe is the tested method -# name. -RSpec/DescribedClass: +# Checks if an example group does not include any tests. +RSpec/EmptyExampleGroup: Enabled: false + CustomIncludeMethods: [] -# Checks for long example. +# Checks for long examples. RSpec/ExampleLength: Enabled: false Max: 5 -# Do not use should when describing your tests. +# Checks that example descriptions do not start with "should". RSpec/ExampleWording: Enabled: false CustomTransform: @@ -795,6 +802,10 @@ RSpec/ExampleWording: not: does not IgnoredWords: [] +# Checks for `expect(...)` calls containing literal values. +RSpec/ExpectActual: + Enabled: false + # Checks the file and folder naming of the spec file. RSpec/FilePath: Enabled: false @@ -806,19 +817,65 @@ RSpec/FilePath: RSpec/Focus: Enabled: true +# Checks the arguments passed to `before`, `around`, and `after`. +RSpec/HookArgument: + Enabled: false + EnforcedStyle: implicit + +# Check that a consistent implict expectation style is used. +# TODO (rspeicher): Available in rubocop-rspec 1.8.0 +# RSpec/ImplicitExpect: +# Enabled: true +# EnforcedStyle: is_expected + # Checks for the usage of instance variables. RSpec/InstanceVariable: Enabled: false -# Checks for multiple top-level describes. +# Checks for `subject` definitions that come after `let` definitions. +RSpec/LeadingSubject: + Enabled: false + +# Checks unreferenced `let!` calls being used for test setup. +RSpec/LetSetup: + Enabled: false + +# Check that chains of messages are not being stubbed. +RSpec/MessageChain: + Enabled: false + +# Checks for consistent message expectation style. +RSpec/MessageExpectation: + Enabled: false + EnforcedStyle: allow + +# Checks for multiple top level describes. RSpec/MultipleDescribes: Enabled: false -# Enforces the usage of the same method on all negative message expectations. +# Checks if examples contain too many `expect` calls. +RSpec/MultipleExpectations: + Enabled: false + Max: 1 + +# Checks for explicitly referenced test subjects. +RSpec/NamedSubject: + Enabled: false + +# Checks for nested example groups. +RSpec/NestedGroups: + Enabled: false + MaxNesting: 2 + +# Checks for consistent method usage for negating expectations. RSpec/NotToNot: EnforcedStyle: not_to Enabled: true +# Checks for stubbed test subjects. +RSpec/SubjectStub: + Enabled: false + # Prefer using verifying doubles over normal doubles. RSpec/VerifiedDoubles: Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 20daf1619a7..87520c67dd5 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,21 +1,21 @@ # This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 0` -# on 2016-07-13 12:36:08 -0600 using RuboCop version 0.41.2. +# on 2016-09-14 15:44:53 -0400 using RuboCop version 0.42.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 154 +# Offense count: 158 Lint/AmbiguousRegexpLiteral: Enabled: false -# Offense count: 43 +# Offense count: 41 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Enabled: false -# Offense count: 14 +# Offense count: 16 Lint/HandleExceptions: Enabled: false @@ -23,28 +23,28 @@ Lint/HandleExceptions: Lint/Loop: Enabled: false -# Offense count: 15 +# Offense count: 16 Lint/ShadowingOuterLocalVariable: Enabled: false -# Offense count: 3 +# Offense count: 6 # Cop supports --auto-correct. Lint/StringConversionInInterpolation: Enabled: false -# Offense count: 44 +# Offense count: 49 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Enabled: false -# Offense count: 129 +# Offense count: 144 # Cop supports --auto-correct. # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. Lint/UnusedMethodArgument: Enabled: false -# Offense count: 12 +# Offense count: 9 # Cop supports --auto-correct. Performance/PushSplat: Enabled: false @@ -59,51 +59,51 @@ Performance/RedundantBlockCall: Performance/RedundantMatch: Enabled: false -# Offense count: 24 +# Offense count: 27 # Cop supports --auto-correct. # Configuration parameters: MaxKeyValuePairs. Performance/RedundantMerge: Enabled: false -# Offense count: 60 +# Offense count: 61 Rails/OutputSafety: Enabled: false -# Offense count: 128 +# Offense count: 129 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: strict, flexible Rails/TimeZone: Enabled: false -# Offense count: 12 +# Offense count: 15 # Cop supports --auto-correct. # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/Validation: Enabled: false -# Offense count: 217 +# Offense count: 273 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: with_first_parameter, with_fixed_indentation Style/AlignParameters: Enabled: false -# Offense count: 32 +# Offense count: 30 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: always, conditionals Style/AndOr: Enabled: false -# Offense count: 47 +# Offense count: 50 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: percent_q, bare_percent Style/BarePercentLiterals: Enabled: false -# Offense count: 258 +# Offense count: 289 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: braces, no_braces, context_dependent @@ -126,14 +126,14 @@ Style/ColonMethodCall: Style/CommentAnnotation: Enabled: false -# Offense count: 34 +# Offense count: 33 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Enabled: false -# Offense count: 789 +# Offense count: 881 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: leading, trailing @@ -144,11 +144,12 @@ Style/DotPosition: Style/DoubleNegation: Enabled: false -# Offense count: 3 +# Offense count: 4 +# Cop supports --auto-correct. Style/EachWithObject: Enabled: false -# Offense count: 30 +# Offense count: 25 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: empty, nil, both @@ -160,7 +161,7 @@ Style/EmptyElse: Style/EmptyLiteral: Enabled: false -# Offense count: 123 +# Offense count: 135 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. Style/ExtraSpacing: @@ -172,16 +173,16 @@ Style/ExtraSpacing: Style/FormatString: Enabled: false -# Offense count: 48 +# Offense count: 51 # Configuration parameters: MinBodyLength. Style/GuardClause: Enabled: false -# Offense count: 11 +# Offense count: 9 Style/IfInsideElse: Enabled: false -# Offense count: 177 +# Offense count: 174 # Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: @@ -194,7 +195,7 @@ Style/IfUnlessModifier: Style/IndentArray: Enabled: false -# Offense count: 89 +# Offense count: 97 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces @@ -208,7 +209,7 @@ Style/IndentHash: Style/Lambda: Enabled: false -# Offense count: 6 +# Offense count: 5 # Cop supports --auto-correct. Style/LineEndConcatenation: Enabled: false @@ -218,17 +219,21 @@ Style/LineEndConcatenation: Style/MethodCallParentheses: Enabled: false -# Offense count: 62 +# Offense count: 8 +Style/MethodMissing: + Enabled: false + +# Offense count: 85 # Cop supports --auto-correct. Style/MutableConstant: Enabled: false -# Offense count: 10 +# Offense count: 8 # Cop supports --auto-correct. Style/NestedParenthesizedCalls: Enabled: false -# Offense count: 12 +# Offense count: 13 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. # SupportedStyles: skip_modifier_ifs, always @@ -242,12 +247,19 @@ Style/Next: Style/NumericLiteralPrefix: Enabled: false +# Offense count: 64 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Enabled: false + # Offense count: 29 # Cop supports --auto-correct. Style/ParallelAssignment: Enabled: false -# Offense count: 208 +# Offense count: 264 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: @@ -265,7 +277,7 @@ Style/PercentQLiterals: Style/PerlBackrefs: Enabled: false -# Offense count: 32 +# Offense count: 35 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. # NamePrefix: is_, has_, have_ # NamePrefixBlacklist: is_, has_, have_ @@ -273,7 +285,7 @@ Style/PerlBackrefs: Style/PredicateName: Enabled: false -# Offense count: 28 +# Offense count: 27 # Cop supports --auto-correct. Style/PreferredHashMethods: Enabled: false @@ -283,14 +295,14 @@ Style/PreferredHashMethods: Style/Proc: Enabled: false -# Offense count: 20 +# Offense count: 22 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: compact, exploded Style/RaiseArgs: Enabled: false -# Offense count: 3 +# Offense count: 4 # Cop supports --auto-correct. Style/RedundantBegin: Enabled: false @@ -300,29 +312,29 @@ Style/RedundantBegin: Style/RedundantException: Enabled: false -# Offense count: 23 +# Offense count: 24 # Cop supports --auto-correct. Style/RedundantFreeze: Enabled: false -# Offense count: 377 +# Offense count: 408 # Cop supports --auto-correct. Style/RedundantSelf: Enabled: false -# Offense count: 94 +# Offense count: 93 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed Style/RegexpLiteral: Enabled: false -# Offense count: 17 +# Offense count: 18 # Cop supports --auto-correct. Style/RescueModifier: Enabled: false -# Offense count: 2 +# Offense count: 5 # Cop supports --auto-correct. Style/SelfAssignment: Enabled: false @@ -339,42 +351,42 @@ Style/SingleLineBlockParams: Style/SingleLineMethods: Enabled: false -# Offense count: 119 +# Offense count: 124 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: space, no_space Style/SpaceBeforeBlockBraces: Enabled: false -# Offense count: 11 +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment. Style/SpaceBeforeFirstArg: Enabled: false -# Offense count: 130 +# Offense count: 141 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space Style/SpaceInsideBlockBraces: Enabled: false -# Offense count: 98 +# Offense count: 96 # Cop supports --auto-correct. Style/SpaceInsideBrackets: Enabled: false -# Offense count: 60 +# Offense count: 62 # Cop supports --auto-correct. Style/SpaceInsideParens: Enabled: false -# Offense count: 5 +# Offense count: 7 # Cop supports --auto-correct. Style/SpaceInsidePercentLiteralDelimiters: Enabled: false -# Offense count: 36 +# Offense count: 40 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles. # SupportedStyles: use_perl_names, use_english_names @@ -388,21 +400,28 @@ Style/SpecialGlobalVars: Style/StringLiteralsInInterpolation: Enabled: false -# Offense count: 24 +# Offense count: 32 # Cop supports --auto-correct. # Configuration parameters: IgnoredMethods. # IgnoredMethods: respond_to, define_method Style/SymbolProc: Enabled: false -# Offense count: 23 +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment. +# SupportedStyles: require_parentheses, require_no_parentheses +Style/TernaryParentheses: + Enabled: false + +# Offense count: 24 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. # SupportedStyles: comma, consistent_comma, no_comma Style/TrailingCommaInArguments: Enabled: false -# Offense count: 113 +# Offense count: 102 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. # SupportedStyles: comma, consistent_comma, no_comma @@ -415,7 +434,7 @@ Style/TrailingCommaInLiteral: Style/TrailingUnderscoreVariable: Enabled: false -# Offense count: 90 +# Offense count: 76 # Cop supports --auto-correct. Style/TrailingWhitespace: Enabled: false @@ -427,12 +446,12 @@ Style/TrailingWhitespace: Style/TrivialAccessors: Enabled: false -# Offense count: 3 +# Offense count: 2 # Cop supports --auto-correct. Style/UnlessElse: Enabled: false -# Offense count: 13 +# Offense count: 14 # Cop supports --auto-correct. Style/UnneededInterpolation: Enabled: false diff --git a/CHANGELOG b/CHANGELOG index de018ca07e4..7f144628d61 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,27 +2,36 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 + - Only check :can_resolve permission if the note is resolvable - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 - Prune events older than 12 months. (ritave) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) + - Fix issues/merge-request templates dropdown for forked projects - Filter tags by name !6121 + - Update gitlab shell secret file also when it is empty. !3774 (glensc) - Give project selection dropdowns responsive width, make non-wrapping. - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 - Pass the "Remember me" value to the U2F authentication form - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) + - Move pushes_since_gc from the database to Redis - Add font color contrast to external label in admin area (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 + - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Fix pagination on user snippets page + - Fix sorting of issues in API + - Ensure specs on sorting of issues in API are deterministic on MySQL - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 + - Fix file permissions change when updating a file on the Gitlab UI !5979 - 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) - Enable pipeline events by default !6278 - Move parsing of sidekiq ps into helper !6245 (pascalbetz) + - Added go to issue boards keyboard shortcut - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Fix blame table layout width @@ -31,6 +40,7 @@ v 8.12.0 (unreleased) - Center build stage columns in pipeline overview (ClemMakesApps) - Fix bug with tooltip not hiding on discussion toggle button - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) + - Fix bug stopping issue description being scrollable after selecting issue template - Remove suggested colors hover underline (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) - Fix project visibility level fields on settings @@ -54,6 +64,7 @@ v 8.12.0 (unreleased) - Use 'git update-ref' for safer web commits !6130 - Sort pipelines requested through the API - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Fix issue boards loading on large screens - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 - Show queued time when showing a pipeline !6084 - Remove unused mixins (ClemMakesApps) @@ -68,13 +79,15 @@ v 8.12.0 (unreleased) - Add last commit time to repo view (ClemMakesApps) - Fix accessibility and visibility of project list dropdown button !6140 - Fix missing flash messages on service edit page (airatshigapov) - - Added project specific enable/disable setting for LFS !5997 + - Added project-specific enable/disable setting for LFS !5997 + - Added group-specific enable/disable setting for LFS !6164 - Don't expose a user's token in the `/api/v3/user` API (!6047) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Ability to manage project issues, snippets, wiki, merge requests and builds access level - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) - Align add button on repository view (ClemMakesApps) - Fix contributions calendar month label truncation (ClemMakesApps) + - Import release note descriptions from GitHub (EspadaV8) - Added tests for diff notes - Add pipeline events to Slack integration !5525 - Add a button to download latest successful artifacts for branches and tags !5142 @@ -86,12 +99,14 @@ v 8.12.0 (unreleased) - Fix repo title alignment (ClemMakesApps) - Change update interval of contacted_at - Fix branch title trailing space on hover (ClemMakesApps) + - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) - Order award emoji tooltips in order they were added (EspadaV8) - 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) + - Gitlab::Checks is now instrumented - Move to project dropdown with infinite scroll for better performance - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz) - Load branches asynchronously in Cherry Pick and Revert dialogs. @@ -110,6 +125,8 @@ v 8.12.0 (unreleased) - Avoid conflict with admin labels when importing GitHub labels - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - Fix repository page ui issues + - Avoid protected branches checks when verifying access without branch name + - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov) - Fixed invisible scroll controls on build page on iPhone - Fix error on raw build trace download for old builds stored in database !4822 - Refactor the triggers page and documentation !6217 @@ -118,8 +135,23 @@ v 8.12.0 (unreleased) - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - Allow bulk update merge requests from merge requests index page - Add notification_settings API calls !5632 (mahcsig) - -v 8.11.6 (unreleased) + - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) + - Fix URLs with anchors in wiki !6300 (houqp) + - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) + - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 + - Fix Gitlab::Popen.popen thread-safety issue + - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) + - Clean environment variables when running git hooks + +v 8.11.6 + - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 + - Make merge conflict file size limit 200 KB, to match the docs. !6052 + - Fix an error where we were unable to create a CommitStatus for running state. !6107 + - Optimize discussion notes resolving and unresolving. !6141 + - Fix GitLab import button. !6167 + - Restore SSH Key title auto-population behavior. !6186 + - Fix DB schema to match latest migration. !6256 + - Exclude some pending or inactivated rows in Member scopes. v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 @@ -324,6 +356,13 @@ v 8.11.0 - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done +v 8.10.9 + - Exclude some pending or inactivated rows in Member scopes + +v 8.10.8 + - Fix information disclosure in issue boards. + - Fix privilege escalation in project import. + v 8.10.7 - Upgrade Hamlit to 2.6.1. !5873 - Upgrade Doorkeeper to 4.2.0. !5881 @@ -549,6 +588,9 @@ v 8.10.0 - Fix migration corrupting import data for old version upgrades - Show tooltip on GitLab export link in new project page +v 8.9.9 + - Exclude some pending or inactivated rows in Member scopes + v 8.9.8 - Upgrade Doorkeeper to 4.2.0. !5881 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c77dcd96a7d..549918284c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,19 +91,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs]. ## Implement design & UI elements -### Design reference - -The GitLab design reference can be found in the [gitlab-design] project. -The designs are made using Antetype (`.atype` files). You can use the -[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design -(the PNG is 1:1). - -The current designs can be found in the [`gitlab8.atype` file]. - -### UI development kit - -Implemented UI elements can also be found at https://gitlab.com/help/ui. Please -note that this page isn't comprehensive at this time. +Please see the [UI Guide for building GitLab]. ## Issue tracker @@ -489,7 +477,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" [scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide" [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide" -[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design -[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12 -[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/ +[UI Guide for building GitLab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/ui_guide.md [license-finder-doc]: doc/development/licensing.md diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 18091983f59..1545d966571 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.4.0 +3.5.0 @@ -26,7 +26,7 @@ gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-cas3', '~> 1.1.2' -gem 'omniauth-facebook', '~> 3.0.0' +gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-google-oauth2', '~> 0.4.1' @@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.6.3' +gem 'gitlab_git', '~> 10.6.6' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes @@ -295,9 +295,10 @@ group :development, :test do gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-teaspoon', '~> 0.0.2' - gem 'rubocop', '~> 0.41.2', require: false - gem 'rubocop-rspec', '~> 1.5.0', require: false + gem 'rubocop', '~> 0.42.0', require: false + gem 'rubocop-rspec', '~> 1.7.0', require: false gem 'scss_lint', '~> 0.47.0', require: false + gem 'haml_lint', '~> 0.18.2', require: false gem 'simplecov', '0.12.0', require: false gem 'flog', '~> 4.3.2', require: false gem 'flay', '~> 2.6.1', require: false diff --git a/Gemfile.lock b/Gemfile.lock index c421713f6a1..1edb218373d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -279,7 +279,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.6.3) + gitlab_git (10.6.6) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -322,11 +322,18 @@ GEM grape-entity (0.4.8) activesupport multi_json (>= 1.3.2) + haml (4.0.7) + tilt + haml_lint (0.18.2) + haml (~> 4.0) + rake (>= 10, < 12) + rubocop (>= 0.36.0) + sysexits (~> 1.1) hamlit (2.6.1) temple (~> 0.7.6) thor tilt - hashie (3.4.3) + hashie (3.4.4) health_check (2.1.0) rails (>= 4.0) hipchat (1.5.2) @@ -394,7 +401,7 @@ GEM mime-types (>= 1.16, < 4) mail_room (0.8.0) method_source (0.8.2) - mime-types (2.99.2) + mime-types (2.99.3) mimemagic (0.3.0) mini_portile2 (2.1.0) minitest (5.7.0) @@ -437,7 +444,7 @@ GEM addressable (~> 2.3) nokogiri (~> 1.6.6) omniauth (~> 1.2) - omniauth-facebook (3.0.0) + omniauth-facebook (4.0.0) omniauth-oauth2 (~> 1.2) omniauth-github (1.1.2) omniauth (~> 1.0) @@ -612,14 +619,14 @@ GEM rspec-retry (0.4.5) rspec-core rspec-support (3.5.0) - rubocop (0.41.2) + rubocop (0.42.0) parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - rubocop-rspec (1.5.0) - rubocop (>= 0.40.0) + rubocop-rspec (1.7.0) + rubocop (>= 0.42.0) ruby-fogbugz (0.2.1) crack (~> 0.4) ruby-prof (0.15.9) @@ -723,6 +730,7 @@ GEM stringex (2.5.2) sys-filesystem (1.1.6) ffi + sysexits (1.2.0) systemu (2.6.5) task_list (1.0.2) html-pipeline @@ -754,7 +762,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.2) - unicode-display_width (1.1.0) + unicode-display_width (1.1.1) unicorn (4.9.0) kgio (~> 2.6) rack @@ -858,7 +866,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.6.3) + gitlab_git (~> 10.6.6) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) @@ -866,6 +874,7 @@ DEPENDENCIES gon (~> 6.1.0) grape (~> 0.15.0) grape-entity (~> 0.4.2) + haml_lint (~> 0.18.2) hamlit (~> 2.6.1) health_check (~> 2.1.0) hipchat (~> 1.5.0) @@ -900,7 +909,7 @@ DEPENDENCIES omniauth-azure-oauth2 (~> 0.0.6) omniauth-bitbucket (~> 0.0.2) omniauth-cas3 (~> 1.1.2) - omniauth-facebook (~> 3.0.0) + omniauth-facebook (~> 4.0.0) omniauth-github (~> 1.1.1) omniauth-gitlab (~> 1.0.0) omniauth-google-oauth2 (~> 0.4.1) @@ -935,8 +944,8 @@ DEPENDENCIES rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.5.0) rspec-retry (~> 0.4.5) - rubocop (~> 0.41.2) - rubocop-rspec (~> 1.5.0) + rubocop (~> 0.42.0) + rubocop-rspec (~> 1.7.0) ruby-fogbugz (~> 0.2.1) ruby-prof (~> 0.15.9) sanitize (~> 2.0) diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index b18b6962382..95352164d76 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -13,6 +13,9 @@ this.buildDropdown(); this.bindEvents(); this.onFilenameUpdate(); + + this.autosizeUpdateEvent = document.createEvent('Event'); + this.autosizeUpdateEvent.initEvent('autosize:update', true, false); } TemplateSelector.prototype.buildDropdown = function() { @@ -72,6 +75,10 @@ TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) { this.editor.setValue(file.content, 1); if (!skipFocus) this.editor.focus(); + + if (this.editor instanceof jQuery) { + this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); + } }; TemplateSelector.prototype.startLoadingSpinner = function() { diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 50fc11d7737..474805c1437 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -34,6 +34,11 @@ }, issues () { this.$nextTick(() => { + if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) { + this.list.page++; + this.list.getIssues(false); + } + if (this.scrollHeight() > this.listHeight()) { this.showCount = true; } else { diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index bea141bae51..087f27d9f4c 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -352,7 +352,13 @@ if (self.options.clicked) { self.options.clicked(selected, $el, e); } - return $el.trigger('blur'); + + // Update label right after all modifications in dropdown has been done + if (self.options.toggleLabel) { + self.updateLabel(selected, $el, self); + } + + $el.trigger('blur'); }); } } @@ -529,7 +535,7 @@ } else { if (!selected) { value = this.options.id ? this.options.id(data) : data.id; - fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName; + fieldName = this.options.fieldName; field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); if (field.length) { @@ -589,6 +595,7 @@ GitLabDropdown.prototype.rowClicked = function(el) { var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; + fieldName = this.options.fieldName; isInput = $(this.el).is('input'); if (this.renderedData) { groupName = el.data('group'); @@ -600,7 +607,6 @@ selectedObject = this.renderedData[selectedIndex]; } } - fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { field = $(this.el); @@ -644,11 +650,6 @@ } } - // Update label right after input has been added - if (this.options.toggleLabel) { - this.updateLabel(selectedObject, el, this); - } - return selectedObject; }; @@ -659,9 +660,6 @@ if (this.options.inputId != null) { $input.attr('id', this.options.inputId); } - if (selectedObject && selectedObject.type) { - $input.attr('data-type', selectedObject.type); - } return this.dropdown.before($input); }; diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index 469e25482bb..b04159420d1 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -34,6 +34,9 @@ Mousetrap.bind('g i', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'); }); + Mousetrap.bind('g l', function() { + ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards'); + }); Mousetrap.bind('g m', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'); }); diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 965fcc06518..46af18580d5 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -162,6 +162,10 @@ ul.content-list { margin-right: 0; } } + + .no-comments { + opacity: 0.5; + } } // When dragging a list item diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 367c7d01944..76b93b23b95 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -79,10 +79,6 @@ padding-left: 15px !important; } - .issue-info, .merge-request-info { - display: none; - } - .nav-links, .nav-links { li a { font-size: 14px; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 63845e6b9ba..41079b6eeb5 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -206,7 +206,7 @@ padding-top: 0; .block { - width: $sidebar_collapsed_width - 1px; + width: $sidebar_collapsed_width - 2px; margin-left: -19px; padding: 15px 0 0; border-bottom: none; diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 7a26b7ad497..3ac34cbc829 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -10,10 +10,6 @@ .issue-labels { display: inline-block; } - - .issue-no-comments { - opacity: 0.5; - } } } @@ -37,6 +33,15 @@ form.edit-issue { margin: 0; } +ul.related-merge-requests > li { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + .merge-request-id { + flex-shrink: 0; + } +} + .merge-requests-title, .related-branches-title { font-size: 16px; font-weight: 600; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 2a44b95de64..96c06086867 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -231,10 +231,6 @@ .merge-request-labels { display: inline-block; } - - .merge-request-no-comments { - opacity: 0.5; - } } .merge-request-angle { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 2d66ab25da6..1b4d12d3053 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -147,14 +147,37 @@ } .stage-cell { - text-align: center; + font-size: 0; svg { height: 18px; width: 18px; + position: relative; + z-index: 2; vertical-align: middle; overflow: visible; } + + .stage-container { + display: inline-block; + position: relative; + margin-right: 6px; + + .tooltip { + white-space: nowrap; + } + + &:not(:last-child) { + &::after { + content: ''; + width: 8px; + position: absolute;; + right: -7px; + bottom: 8px; + border-bottom: 2px solid $border-color; + } + } + } } .duration, @@ -318,9 +341,17 @@ .build-content { width: 130px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + + .ci-status-text { + width: 110px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + display: inline-block; + position: relative; + top: -1px; + } a { color: $layout-link-gray; @@ -331,13 +362,74 @@ text-decoration: underline; } } + } + + .dropdown-menu-toggle { + border: none; + width: auto; + padding: 0; + color: $layout-link-gray; + + .ci-status-text { + width: 80px; + } + } + + .grouped-pipeline-dropdown { + padding: 8px 0; + width: 200px; + left: auto; + right: -214px; + top: -9px; + + a:hover { + .ci-status-text { + text-decoration: none; + } + } + .ci-status-text { + width: 145px; + } + + .arrow { + &:before, + &:after { + content: ''; + display: inline-block; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 18px; + } + + &:before { + left: -5px; + margin-top: -6px; + border-width: 7px 5px 7px 0; + border-right-color: $border-color; + } + + &:after { + left: -4px; + margin-top: -9px; + border-width: 10px 7px 10px 0; + border-right-color: $white-light; + } + } + } + + .badge { + background-color: $gray-dark; + color: $layout-link-gray; + font-weight: normal; } } svg { - position: relative; - top: 2px; + vertical-align: middle; margin-right: 5px; } @@ -442,7 +534,7 @@ width: 21px; height: 25px; position: absolute; - top: -28.5px; + top: -29px; border-top: 2px solid $border-color; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 3e6e50375f6..db46d8072ce 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -334,6 +334,10 @@ a.deploy-project-label { a { color: $gl-dark-link-color; } + + .dropdown-menu { + width: 240px; + } } .last-push-widget { diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index 2aa939b7dc3..5270aea4e79 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -2,20 +2,6 @@ padding: 2px; } -.snippet-holder { - margin-bottom: -$gl-padding; - - .file-holder { - border-top: 0; - } - - .file-actions { - .btn-clipboard { - @extend .btn; - } - } -} - .markdown-snippet-copy { position: fixed; top: -10px; @@ -24,29 +10,18 @@ max-width: 0; } -.file-holder.snippet-file-content { - padding-bottom: $gl-padding; - border-bottom: 1px solid $border-color; - - .file-title { - padding-top: $gl-padding; - padding-bottom: $gl-padding; - } - - .file-actions { - top: 12px; - } - - .file-content { - border-left: 1px solid $border-color; - border-right: 1px solid $border-color; - border-bottom: 1px solid $border-color; +.snippet-file-content { + border-radius: 3px; + .btn-clipboard { + @extend .btn; } } .snippet-title { font-size: 24px; - font-weight: normal; + font-weight: 600; + padding: $gl-padding; + padding-left: 0; } .snippet-actions { diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index cdfa8d91a28..aed77d0358a 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -60,6 +60,14 @@ class Admin::GroupsController < Admin::ApplicationController end def group_params - params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled) + params.require(:group).permit( + :avatar, + :description, + :lfs_enabled, + :name, + :path, + :request_access_enabled, + :visibility_level + ) end end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index f2b8f297bc2..dacb5679dd3 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -7,8 +7,7 @@ module CreatesCommit commit_params = @commit_params.merge( source_project: @project, source_branch: @ref, - target_branch: @target_branch, - previous_path: @previous_path + target_branch: @target_branch ) result = service.new(@tree_edit_project, current_user, commit_params).execute diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index cb82d62616c..b83c3a872cf 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -121,7 +121,17 @@ class GroupsController < Groups::ApplicationController end def group_params - params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled) + params.require(:group).permit( + :avatar, + :description, + :lfs_enabled, + :name, + :path, + :public, + :request_access_enabled, + :share_with_group_lock, + :visibility_level + ) end def load_events diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index cdf9a04bacf..b78cc6585ba 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -38,12 +38,7 @@ class Projects::BlobController < Projects::ApplicationController end def update - if params[:file_path].present? - @previous_path = @path - @path = params[:file_path] - @commit_params[:file_path] = @path - end - + @path = params[:file_path] if params[:file_path].present? after_edit_path = if from_merge_request && @target_branch == @ref diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + @@ -143,6 +138,8 @@ class Projects::BlobController < Projects::ApplicationController params[:file_name] = params[:file].original_filename end File.join(@path, params[:file_name]) + elsif params[:file_path].present? + params[:file_path] else @path end @@ -155,6 +152,7 @@ class Projects::BlobController < Projects::ApplicationController @commit_params = { file_path: @file_path, commit_message: params[:commit_message], + previous_path: @path, file_content: params[:content], file_content_encoding: params[:encoding], last_commit_sha: params[:last_commit_sha] diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 77934ff9962..f13fb1df373 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -74,7 +74,7 @@ class Projects::BuildsController < Projects::ApplicationController def erase @build.erase(erased_by: current_user) redirect_to namespace_project_build_path(project.namespace, project, @build), - notice: "Build has been sucessfully erased!" + notice: "Build has been successfully erased!" end def raw diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8895cb955bd..aa8645ba8cc 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -428,17 +428,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def validates_merge_request - # If source project was removed (Ex. mr from fork to origin) - return invalid_mr unless @merge_request.source_project - # Show git not found page # if there is no saved commits between source & target branch if @merge_request.commits.blank? # and if target branch doesn't exist return invalid_mr unless @merge_request.target_branch_exists? - - # or if source branch doesn't exist - return invalid_mr unless @merge_request.source_branch_exists? end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index b9211e88473..ab880ed6de0 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -23,4 +23,29 @@ module GroupsHelper full_title end end + + def projects_lfs_status(group) + lfs_status = + if group.lfs_enabled? + group.projects.select(&:lfs_enabled?).size + else + group.projects.reject(&:lfs_enabled?).size + end + + size = group.projects.size + + if lfs_status == size + 'for all projects' + else + "for #{lfs_status} out of #{pluralize(size, 'project')}" + end + end + + def group_lfs_status(group) + status = group.lfs_enabled? ? 'enabled' : 'disabled' + + content_tag(:span, class: "lfs-#{status}") do + "#{status.humanize} #{projects_lfs_status(group)}" + end + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 16a8e52a4ca..56477733ea2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -27,7 +27,7 @@ module ProjectsHelper author_html = "" # Build avatar image tag - author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar] + author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar] # Build name span tag if opts[:by_username] diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index e523c46e879..8a7446b7cc7 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -30,6 +30,37 @@ module SearchHelper "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\"" end + def parse_search_result(result) + ref = nil + filename = nil + basename = nil + startline = 0 + + result.each_line.each_with_index do |line, index| + if line =~ /^.*:.*:\d+:/ + ref, filename, startline = line.split(':') + startline = startline.to_i - index + extname = Regexp.escape(File.extname(filename)) + basename = filename.sub(/#{extname}$/, '') + break + end + end + + data = "" + + result.each_line do |line| + data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') + end + + OpenStruct.new( + filename: filename, + basename: basename, + ref: ref, + startline: startline, + data: data + ) + end + private # Autocomplete results for various settings pages diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 0a5a8eb5aee..7e33a562077 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -1,10 +1,10 @@ module SnippetsHelper - def reliable_snippet_path(snippet) + def reliable_snippet_path(snippet, opts = nil) if snippet.project_id? namespace_project_snippet_path(snippet.project.namespace, - snippet.project, snippet) + snippet.project, snippet, opts) else - snippet_path(snippet) + snippet_path(snippet, opts) end end diff --git a/app/models/blob.rb b/app/models/blob.rb index 12cc5aaafba..ab92e820335 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -22,6 +22,18 @@ class Blob < SimpleDelegator new(blob) end + # Returns the data of the blob. + # + # If the blob is a text based blob the content is converted to UTF-8 and any + # invalid byte sequences are replaced. + def data + if binary? + super + else + @data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) + end + end + def no_highlighting? size && size > 1.megabyte end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 61052437318..fb16bc06d71 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -148,6 +148,7 @@ module Ci variables += runner.predefined_variables if runner variables += project.container_registry_variables variables += yaml_variables + variables += user_variables variables += project.secret_variables variables += trigger_request.user_variables if trigger_request variables @@ -434,6 +435,15 @@ module Ci read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || [] end + def user_variables + return [] if user.blank? + + [ + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } + ] + end + private def update_artifacts_size @@ -469,6 +479,7 @@ module Ci ] variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag? variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request + variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if manual? variables end diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index c9c47ec7419..6959223aed9 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -1,7 +1,7 @@ module Ci class Variable < ActiveRecord::Base extend Ci::Model - + belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id validates_uniqueness_of :key, scope: :gl_project_id @@ -11,7 +11,9 @@ module Ci format: { with: /\A[a-zA-Z0-9_]+\z/, message: "can contain only letters, digits and '_'." } - attr_encrypted :value, + scope :order_key_asc, -> { reorder(key: :asc) } + + attr_encrypted :value, mode: :per_attribute_iv_and_salt, insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 4a628924499..c85561291c8 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - # We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed - around_transition any => [:success, :failed, :canceled] do |commit_status, block| - block.call - - commit_status.pipeline.try(:process!) - end - after_transition do |commit_status, transition| commit_status.pipeline.try(:build_updated) unless transition.loopback? end + after_transition any => [:success, :failed, :canceled] do |commit_status| + commit_status.pipeline.try(:process!) + true + end + after_transition [:created, :pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) end @@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base pipeline.before_sha || Gitlab::Git::BLANK_SHA end + def group_name + name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip + end + def self.stages # We group by stage name, but order stages by theirs' index unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') @@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base allow_failure? && (failed? || canceled?) end + def playable? + false + end + def duration calculate_duration end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index f7b8352405c..d658552f695 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -8,8 +8,9 @@ module HasStatus class_methods do def status_sql - scope = all.relevant + scope = all builds = scope.select('count(*)').to_sql + created = scope.created.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored) ignored ||= '0' @@ -19,12 +20,12 @@ module HasStatus skipped = scope.skipped.select('count(*)').to_sql deduce_status = "(CASE - WHEN (#{builds})=0 THEN NULL + WHEN (#{builds})=(#{created}) THEN NULL WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' - WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending' + WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending' WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' - WHEN (#{running})+(#{pending})>0 THEN 'running' + WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running' ELSE 'failed' END)" diff --git a/app/models/group.rb b/app/models/group.rb index c48869ae465..aefb94b2ada 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -95,6 +95,13 @@ class Group < Namespace end end + def lfs_enabled? + return false unless Gitlab.config.lfs.enabled + return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil? + + self[:lfs_enabled] + end + def add_users(user_ids, access_level, current_user: nil, expires_at: nil) user_ids.each do |user_id| Member.add_user( diff --git a/app/models/member.rb b/app/models/member.rb index 64e0d33fb20..69406379948 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -28,17 +28,34 @@ class Member < ActiveRecord::Base allow_nil: true } + # This scope encapsulates (most of) the conditions a row in the member table + # must satisfy if it is a valid permission. Of particular note: + # + # * Access requests must be excluded + # * Blocked users must be excluded + # * Invitations take effect immediately + # * expires_at is not implemented. A background worker purges expired rows + scope :active, -> do + is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil)) + user_is_active = User.arel_table[:state].eq(:active) + + includes(:user).references(:users) + .where(is_external_invite.or(user_is_active)) + .where(requested_at: nil) + end + scope :invite, -> { where.not(invite_token: nil) } scope :non_invite, -> { where(invite_token: nil) } scope :request, -> { where.not(requested_at: nil) } - scope :has_access, -> { where('access_level > 0') } - - scope :guests, -> { where(access_level: GUEST) } - scope :reporters, -> { where(access_level: REPORTER) } - scope :developers, -> { where(access_level: DEVELOPER) } - scope :masters, -> { where(access_level: MASTER) } - scope :owners, -> { where(access_level: OWNER) } - scope :owners_and_masters, -> { where(access_level: [OWNER, MASTER]) } + + scope :has_access, -> { active.where('access_level > 0') } + + scope :guests, -> { active.where(access_level: GUEST) } + scope :reporters, -> { active.where(access_level: REPORTER) } + scope :developers, -> { active.where(access_level: DEVELOPER) } + scope :masters, -> { active.where(access_level: MASTER) } + scope :owners, -> { active.where(access_level: OWNER) } + scope :owners_and_masters, -> { active.where(access_level: [OWNER, MASTER]) } before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b0b1313f94a..f7d1253d957 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -316,6 +316,10 @@ class MergeRequest < ActiveRecord::Base closed? && forked_source_project_missing? end + def closed_without_source_project? + closed? && !source_project + end + def forked_source_project_missing? return false unless for_fork? return true unless source_project @@ -323,6 +327,12 @@ class MergeRequest < ActiveRecord::Base !source_project.forked_from?(target_project) end + def reopenable? + return false if closed_without_fork? || closed_without_source_project? || merged? + + closed? + end + def ensure_merge_request_diff merge_request_diff || create_merge_request_diff end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 7c29d27ce97..919b3b1f095 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -141,6 +141,11 @@ class Namespace < ActiveRecord::Base projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id) end + def lfs_enabled? + # User namespace will always default to the global setting + Gitlab.config.lfs.enabled + end + private def repository_storage_paths diff --git a/app/models/project.rb b/app/models/project.rb index a6de2c48071..8b5a6f167bd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -58,7 +58,7 @@ class Project < ActiveRecord::Base # Relations belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' - belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' + belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id' belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' @@ -393,10 +393,9 @@ class Project < ActiveRecord::Base end def lfs_enabled? - return false unless Gitlab.config.lfs.enabled - return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil? + return namespace.lfs_enabled? if self[:lfs_enabled].nil? - self[:lfs_enabled] + self[:lfs_enabled] && Gitlab.config.lfs.enabled end def repository_storage_path @@ -1288,8 +1287,24 @@ class Project < ActiveRecord::Base end end + def pushes_since_gc + Gitlab::Redis.with { |redis| redis.get(pushes_since_gc_redis_key).to_i } + end + + def increment_pushes_since_gc + Gitlab::Redis.with { |redis| redis.incr(pushes_since_gc_redis_key) } + end + + def reset_pushes_since_gc + Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) } + end + private + def pushes_since_gc_redis_key + "projects/#{id}/pushes_since_gc" + end + # Prevents the creation of project_feature record for every project def setup_project_feature build_project_feature unless project_feature diff --git a/app/models/repository.rb b/app/models/repository.rb index 7b7090b8a73..c69e5a22a69 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -813,7 +813,7 @@ class Repository update: true } - if previous_path + if previous_path && previous_path != path options[:file][:previous_path] = previous_path Gitlab::Git::Blob.rename(raw_repository, options) else @@ -990,37 +990,6 @@ class Repository Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) end - def parse_search_result(result) - ref = nil - filename = nil - basename = nil - startline = 0 - - result.each_line.each_with_index do |line, index| - if line =~ /^.*:.*:\d+:/ - ref, filename, startline = line.split(':') - startline = startline.to_i - index - extname = Regexp.escape(File.extname(filename)) - basename = filename.sub(/#{extname}$/, '') - break - end - end - - data = "" - - result.each_line do |line| - data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') - end - - OpenStruct.new( - filename: filename, - basename: basename, - ref: ref, - startline: startline, - data: data - ) - end - def fetch_ref(source_path, source_ref, target_ref) args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) Gitlab::Popen.popen(args, path_to_repo) @@ -1048,7 +1017,7 @@ class Repository GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do update_ref!(ref, newrev, oldrev) - + if was_empty || !target_branch # If repo was empty expire cache after_create if was_empty diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index de48a50774e..36c93dddadb 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -31,13 +31,13 @@ module Ci current_status = status_for_prior_stages(index) created_builds_in_stage(index).select do |build| - process_build(build, current_status) + if HasStatus::COMPLETED_STATUSES.include?(current_status) + process_build(build, current_status) + end end end def process_build(build, current_status) - return false unless HasStatus::COMPLETED_STATUSES.include?(current_status) - if valid_statuses_for_when(build.when).include?(current_status) build.enqueue true diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index ed73d8cb8c2..1c82599c579 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -16,11 +16,29 @@ module Commits error(ex.message) end + private + def commit raise NotImplementedError end - private + def commit_change(action) + raise NotImplementedError unless repository.respond_to?(action) + + into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch + tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch) + + if tree_id + create_target_branch(into) if @create_merge_request + + repository.public_send(action, current_user, @commit, into, tree_id) + success + else + error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically. + It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content." + raise ChangeError, error_msg + end + end def check_push_permissions allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch) diff --git a/app/services/commits/cherry_pick_service.rb b/app/services/commits/cherry_pick_service.rb index f9a4efa7182..605cca36f9c 100644 --- a/app/services/commits/cherry_pick_service.rb +++ b/app/services/commits/cherry_pick_service.rb @@ -1,19 +1,7 @@ module Commits class CherryPickService < ChangeService def commit - cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch - cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch) - - if cherry_pick_tree_id - create_target_branch(cherry_pick_into) if @create_merge_request - - repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id) - success - else - error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically. - It may have already been cherry-picked, or a more recent commit may have updated some of its content." - raise ChangeError, error_msg - end + commit_change(:cherry_pick) end end end diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb index c7de9f6f35e..addd55cb32f 100644 --- a/app/services/commits/revert_service.rb +++ b/app/services/commits/revert_service.rb @@ -1,19 +1,7 @@ module Commits class RevertService < ChangeService def commit - revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch - revert_tree_id = repository.check_revert_content(@commit, @target_branch) - - if revert_tree_id - create_target_branch(revert_into) if @create_merge_request - - repository.revert(current_user, @commit, revert_into, revert_tree_id) - success - else - error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically. - It may have already been reverted, or a more recent commit may have updated some of its content." - raise ChangeError, error_msg - end + commit_change(:revert) end end end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 8a53f65aec1..a08c6fcd94b 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -27,6 +27,8 @@ module Projects # Git data (e.g. a list of branch names). flush_caches(project, wiki_path) + Projects::UnlinkForkService.new(project, current_user).execute + Project.transaction do project.destroy! diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index 29b3981f49f..c3dfc8cfbe8 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -30,10 +30,8 @@ module Projects end def increment! - if Gitlab::ExclusiveLease.new("project_housekeeping:increment!:#{@project.id}", timeout: 60).try_obtain - Gitlab::Metrics.measure(:increment_pushes_since_gc) do - update_pushes_since_gc(@project.pushes_since_gc + 1) - end + Gitlab::Metrics.measure(:increment_pushes_since_gc) do + @project.increment_pushes_since_gc end end @@ -43,14 +41,10 @@ module Projects GitGarbageCollectWorker.perform_async(@project.id) ensure Gitlab::Metrics.measure(:reset_pushes_since_gc) do - update_pushes_since_gc(0) + @project.reset_pushes_since_gc end end - def update_pushes_since_gc(new_value) - @project.update_column(:pushes_since_gc, new_value) - end - def try_obtain_lease Gitlab::Metrics.measure(:obtain_housekeeping_lease) do lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml deleted file mode 100644 index f29d9c94441..00000000000 --- a/app/views/admin/builds/_build.html.haml +++ /dev/null @@ -1,77 +0,0 @@ -- project = build.project -%tr.build.commit - %td.status - = ci_status_with_icon(build.status) - - %td - .branch-commit - - if can?(current_user, :read_build, build.project) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span.build-link ##{build.id} - - else - %span.build-link ##{build.id} - - - if build.ref - .icon-container - = build.tag? ? icon('tag') : icon('code-fork') - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - - else - .light none - .icon-container - = custom_icon("icon_commit") - - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id" - - if build.stuck? - %i.fa.fa-warning.text-warning - - .label-container - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %span.label.label-info triggered - - if build.try(:allow_failure) - %span.label.label-danger allowed to fail - - %td - - if project - = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) - - %td - - if build.try(:runner) - = runner_link(build.runner) - - else - .light none - - %td - #{build.stage} / #{build.name} - - %td - - if build.duration - %p.duration - = custom_icon("icon_timer") - = duration_in_numbers(build.duration) - - - if build.finished_at - %p.finished-at - = icon("calendar") - %span #{time_ago_with_tooltip(build.finished_at)} - - - if defined?(coverage) && coverage - %td.coverage - - if build.try(:coverage) - #{build.coverage}% - - %td - .pull-right - - if can?(current_user, :read_build, project) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do - %i.fa.fa-download - - if can?(current_user, :update_build, build.project) - - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do - %i.fa.fa-remove.cred - - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - %i.fa.fa-refresh diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 3d77634d8fa..26a8846b609 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -4,26 +4,8 @@ %div{ class: container_class } .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to admin_builds_path do - All - %span.badge.js-totalbuilds-count= @all_builds.count(:id) - - %li{class: ('active' if @scope == 'pending')} - = link_to admin_builds_path(scope: :pending) do - Pending - %span.badge= number_with_delimiter(@all_builds.pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to admin_builds_path(scope: :running) do - Running - %span.badge= number_with_delimiter(@all_builds.running.count(:id)) - - %li{class: ('active' if @scope == 'finished')} - = link_to admin_builds_path(scope: :finished) do - Finished - %span.badge= number_with_delimiter(@all_builds.finished.count(:id)) + - build_path_proc = ->(scope) { admin_builds_path(scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope .nav-controls - if @all_builds.running_or_pending.any? @@ -33,23 +15,4 @@ #{(@scope || 'all').capitalize} builds %ul.content-list.builds-content-list - - if @builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit - %th Project - %th Runner - %th Name - %th - %th - - - @builds.each do |build| - = render "admin/builds/build", build: build - - = paginate @builds, theme: 'gitlab' + = render "projects/builds/table", builds: @builds, admin: true diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 5f7fdfdb011..817910f7ddf 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -13,6 +13,8 @@ .col-sm-offset-2.col-sm-10 = render 'shared/allow_request_access', form: f + = render 'groups/group_lfs_settings', f: f + - if @group.new_record? .form-group .col-sm-offset-2.col-sm-10 diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index bb374694400..0188ed448ce 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -37,6 +37,12 @@ %strong = @group.created_at.to_s(:medium) + %li + %span.light Group Git LFS status: + %strong + = group_lfs_status(@group) + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + .panel.panel-default .panel-heading %h3.panel-title diff --git a/app/views/groups/_group_lfs_settings.html.haml b/app/views/groups/_group_lfs_settings.html.haml new file mode 100644 index 00000000000..af57065f0fc --- /dev/null +++ b/app/views/groups/_group_lfs_settings.html.haml @@ -0,0 +1,11 @@ +- if current_user.admin? + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :lfs_enabled do + = f.check_box :lfs_enabled, checked: @group.lfs_enabled? + %strong + Allow projects within this group to use Git LFS + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + %br/ + %span.descr This setting can be overridden in each project.
\ No newline at end of file diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index decb89b2fd6..c766370d5a0 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -25,6 +25,8 @@ .col-sm-offset-2.col-sm-10 = render 'shared/allow_request_access', form: f + = render 'group_lfs_settings', f: f + .form-group %hr = f.label :share_with_group_lock, class: 'control-label' do diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 16c16cec137..65842a0479b 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -165,6 +165,12 @@ %tr %td.shortcut .key g + .key l + %td + Go to issue boards + %tr + %td.shortcut + .key g .key m %td Go to merge requests diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index f7012595a5a..8e4937b7aa0 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -113,3 +113,7 @@ %li.hidden = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do Commits + + -# Shortcut to issue boards + %li.hidden + = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index a42b3b8eb38..93187873501 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,4 +1,5 @@ - page_title "SSH Keys" += render 'profiles/head' .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml index 249680bcab6..de1337a2a24 100644 --- a/app/views/profiles/update_username.js.haml +++ b/app/views/profiles/update_username.js.haml @@ -1,6 +1,6 @@ - if @user.valid? :plain - new Flash("Username sucessfully changed", "notice") + new Flash("Username successfully changed", "notice") - else :plain new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml new file mode 100644 index 00000000000..61eff73da26 --- /dev/null +++ b/app/views/projects/builds/_table.html.haml @@ -0,0 +1,24 @@ +- admin = local_assigns.fetch(:admin, false) + +- if builds.blank? + %li + .nothing-here-block No builds to show +- else + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Commit + - if admin + %th Project + %th Runner + %th Stage + %th Name + %th + %th Coverage + %th + + = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: admin || project.build_coverage_enabled?, admin: admin } + + = paginate builds, theme: 'gitlab' diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 2af625f69cd..5c60b7a7364 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -4,30 +4,8 @@ %div{ class: container_class } .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to project_builds_path(@project) do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_builds.count(:id)) - - %li{class: ('active' if @scope == 'pending')} - = link_to project_builds_path(@project, scope: :pending) do - Pending - %span.badge - = number_with_delimiter(@all_builds.pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to project_builds_path(@project, scope: :running) do - Running - %span.badge - = number_with_delimiter(@all_builds.running.count(:id)) - - %li{class: ('active' if @scope == 'finished')} - = link_to project_builds_path(@project, scope: :finished) do - Finished - %span.badge - = number_with_delimiter(@all_builds.finished.count(:id)) + - build_path_proc = ->(scope) { project_builds_path(@project, scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope .nav-controls - if can?(current_user, :update_build, @project) @@ -42,23 +20,4 @@ %span CI Lint %ul.content-list.builds-content-list - - if @builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit - %th Stage - %th Name - %th - - if @project.build_coverage_enabled? - %th Coverage - %th - - = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? - - = paginate @builds, theme: 'gitlab' + = render "table", builds: @builds, project: @project diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 73de8abe55b..75192c48188 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,3 +1,11 @@ +- admin = local_assigns.fetch(:admin, false) +- ref = local_assigns.fetch(:ref, nil) +- commit_sha = local_assigns.fetch(:commit_sha, nil) +- retried = local_assigns.fetch(:retried, false) +- stage = local_assigns.fetch(:stage, false) +- coverage = local_assigns.fetch(:coverage, false) +- allow_retry = local_assigns.fetch(:allow_retry, false) + %tr.build.commit %td.status - if can?(current_user, :read_build, build) @@ -9,11 +17,11 @@ .branch-commit - if can?(current_user, :read_build, build) = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span ##{build.id} + %span.build-link ##{build.id} - else - %span ##{build.id} + %span.build-link ##{build.id} - - if defined?(ref) && ref + - if ref - if build.ref .icon-container = build.tag? ? icon('tag') : icon('code-fork') @@ -23,12 +31,12 @@ .icon-container = custom_icon("icon_commit") - - if defined?(commit_sha) && commit_sha + - if commit_sha = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" - if build.stuck? = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if defined?(retried) && retried + - if retried = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') .label-container @@ -40,19 +48,24 @@ %span.label.label-info triggered - if build.try(:allow_failure) %span.label.label-danger allowed to fail - - if defined?(retried) && retried + - if retried %span.label.label-warning retried - if build.manual? %span.label.label-info manual - - if defined?(runner) && runner + - if admin + %td + - if build.project + = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) + + - if admin %td - if build.try(:runner) = runner_link(build.runner) - else .light none - - if defined?(stage) && stage + - if stage %td = build.stage @@ -64,13 +77,14 @@ %p.duration = custom_icon("icon_timer") = duration_in_numbers(build.duration) + - if build.finished_at %p.finished-at = icon("calendar") %span #{time_ago_with_tooltip(build.finished_at)} - - if defined?(coverage) && coverage - %td.coverage + %td.coverage + - if coverage - if build.try(:coverage) #{build.coverage}% @@ -83,10 +97,10 @@ - if build.active? = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - - elsif defined?(allow_retry) && allow_retry + - elsif allow_retry - if build.retryable? = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do = icon('repeat') - - elsif build.playable? + - elsif build.playable? && !admin = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = custom_icon('icon_play') diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 36fb0300aeb..547bc0c9c19 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,15 +1,12 @@ - is_playable = subject.playable? && can?(current_user, :update_build, @project) -%li.build{class: ("playable" if is_playable)} - .curve - .build-content - - if is_playable - = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do - = render_status_with_link('build', 'play') - %span.ci-status-text= subject.name - - elsif can?(current_user, :read_build, @project) - = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = render_status_with_link('build', subject.status) - %span.ci-status-text= subject.name - - else - = render_status_with_link('build', subject.status) - = ci_icon_for_status(subject.status) +- if is_playable + = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do + = render_status_with_link('build', 'play') + .ci-status-text= subject.name +- elsif can?(current_user, :read_build, @project) + = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do + = render_status_with_link('build', subject.status) + .ci-status-text= subject.name +- else + = render_status_with_link('build', subject.status) + = ci_icon_for_status(subject.status) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index bb9493f5158..6391c67021b 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -36,16 +36,14 @@ - stages_status = pipeline.statuses.relevant.latest.stages_status - - stages.each do |stage| - %td.stage-cell + %td.stage-cell + - stages.each do |stage| - status = stages_status[stage] - tooltip = "#{stage.titleize}: #{status || 'not found'}" - if status - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do - = ci_icon_for_status(status) - - else - .light.has-tooltip{ title: tooltip } - \- + .stage-container + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do + = ci_icon_for_status(status) %td - if pipeline.duration diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 20a85148ab5..9258f4b3c25 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -39,8 +39,7 @@ = stage.titleize .builds-container %ul - - statuses.each do |status| - = render "projects/#{status.to_partial_path}_pipeline", subject: status + = render "projects/commit/pipeline_stage", statuses: statuses - if pipeline.yaml_errors.present? diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml new file mode 100644 index 00000000000..23c5c51fbc2 --- /dev/null +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -0,0 +1,14 @@ +- status_groups = statuses.group_by(&:group_name) +- status_groups.each do |group_name, grouped_statuses| + - if grouped_statuses.one? + - status = grouped_statuses.first + - is_playable = status.playable? && can?(current_user, :update_build, @project) + %li.build{ class: ("playable" if is_playable) } + .curve + .build-content + = render "projects/#{status.to_partial_path}_pipeline", subject: status + - else + %li.build + .curve + .build-content + = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml new file mode 100644 index 00000000000..4e7a6f1af08 --- /dev/null +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -0,0 +1,11 @@ +- group_status = CommitStatus.where(id: subject).status += render_status_with_link('build', group_status) +.dropdown.inline + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } + %span.ci-status-text + = name + %span.badge= subject.size + %ul.dropdown-menu.grouped-pipeline-dropdown + .arrow + - subject.each do |status| + = render "projects/#{status.to_partial_path}_pipeline", subject: status diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f6d751a343e..a04d53e02bf 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -84,15 +84,14 @@ = project_feature_access_select(:snippets_access_level) - if Gitlab.config.lfs.enabled && current_user.admin? - .form-group - .checkbox - = f.label :lfs_enabled do - = f.check_box :lfs_enabled, checked: @project.lfs_enabled? - %strong LFS - %br - %span.descr - Git Large File Storage - = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + .row + .col-md-9 + = f.label :lfs_enabled, 'LFS', class: 'label-light' + %span.help-block + Git Large File Storage + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + .col-md-3 + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control' - if Gitlab.config.registry.enabled .form-group diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 576d0bec51b..409f4701e4b 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,10 +1,7 @@ -%li.build - .curve - .build-content - - if subject.target_url - - link_to subject.target_url do - = render_status_with_link('commit status', subject.status) - %span.ci-status-text= subject.name - - else - = render_status_with_link('commit status', subject.status) - %span.ci-status-text= subject.name +- if subject.target_url + = link_to subject.target_url do + = render_status_with_link('commit status', subject.status) + %span.ci-status-text= subject.name +- else + = render_status_with_link('commit status', subject.status) + %span.ci-status-text= subject.name diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 851d4c06990..8b1a8a8a2d9 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -29,7 +29,7 @@ - note_count = issue.notes.user.count %li - = link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do + = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do = icon('comments') = note_count diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index d8075371853..31d3ec23276 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -1,7 +1,7 @@ - if @merge_requests.any? %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') - %ul.unstyled-list + %ul.unstyled-list.related-merge-requests - has_any_ci = @merge_requests.any?(&:pipeline) - @merge_requests.each do |merge_request| %li diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index a8eeab3e55e..44683c8bcdb 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -1,7 +1,7 @@ - if @related_branches.any? %h2.related-branches-title = pluralize(@related_branches.count, 'Related Branch') - %ul.unstyled-list + %ul.unstyled-list.related-merge-requests - @related_branches.each do |branch| %li - target = @project.repository.find_branch(branch).target diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index d070979bcfe..3900b4f6f17 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -2,7 +2,7 @@ - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"} - - if @merge_request.closed? + - if @merge_request.reopenable? = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" } %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } } diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 31f8d0aeb5b..68fb7d5a414 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -41,7 +41,7 @@ - note_count = merge_request.mr_and_commit_notes.user.count %li - = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do + = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do = icon('comments') = note_count diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 4b4d418e8ec..d03ff9ec7e8 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -29,17 +29,19 @@ %ul.dropdown-menu.dropdown-menu-align-right %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) - .normal - %span Request to merge - %span.label-branch= source_branch_with_namespace(@merge_request) - %span into - %span.label-branch - = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) - - if @merge_request.open? && @merge_request.diverged_from_target_branch? - %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) + - unless @merge_request.closed_without_fork? + .normal + %span Request to merge + %span.label-branch= source_branch_with_namespace(@merge_request) + %span into + %span.label-branch + = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) + - if @merge_request.open? && @merge_request.diverged_from_target_branch? + %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) - = render "projects/merge_requests/show/how_to_merge" - = render "projects/merge_requests/widget/show.html.haml" + - unless @merge_request.closed_without_source_project? + = render "projects/merge_requests/show/how_to_merge" + = render "projects/merge_requests/widget/show.html.haml" - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user) .light.prepend-top-default.append-bottom-default @@ -53,10 +55,11 @@ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion %span.badge= @merge_request.mr_and_commit_notes.user.count - %li.commits-tab - = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do - Commits - %span.badge= @commits_count + - unless @merge_request.closed_without_source_project? + %li.commits-tab + = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do + Commits + %span.badge= @commits_count - if @pipeline %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 7c82177f9ea..9ec17cf6e76 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -1,6 +1,5 @@ - return unless note.author - return if note.cross_reference_not_visible_for?(current_user) -- can_resolve = can?(current_user, :resolve_note, note) - note_editable = note_editable?(note) %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} } @@ -24,6 +23,8 @@ %span.note-role.hidden-xs= access - if note.resolvable? + - can_resolve = can?(current_user, :resolve_note, note) + %resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'", ":project-path" => "'#{note.project.path}'", ":discussion-id" => "'#{note.discussion_id}'", diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4d957e0d890..faf28db68d1 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -47,13 +47,7 @@ %tbody %th Status %th Commit - - stages.each do |stage| - %th.stage - - if stage.titleize.length > 12 - %span.has-tooltip{ title: "#{stage.titleize}" } - = stage.titleize - - else - = stage.titleize + %th Stages %th %th = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index bdbf3e5f4d6..a5a5619fa12 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -3,11 +3,11 @@ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do New Snippet - if can?(current_user, :update_project_snippet, @snippet) - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do - Edit - - if can?(current_user, :update_project_snippet, @snippet) = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do Delete + - if can?(current_user, :update_project_snippet, @snippet) + = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do + Edit - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } @@ -21,9 +21,9 @@ New Snippet - if can?(current_user, :update_project_snippet, @snippet) %li - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do - Edit - - if can?(current_user, :update_project_snippet, @snippet) - %li = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do Delete + - if can?(current_user, :update_project_snippet, @snippet) + %li + = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do + Edit diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index bae4d8f349f..b70fda88a79 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -1,15 +1,14 @@ - page_title @snippet.title, "Snippets" -.snippet-holder - = render 'shared/snippets/header' += render 'shared/snippets/header' - %article.file-holder.file-holder-no-border.snippet-file-content - .file-title.file-title-clear - = blob_icon 0, @snippet.file_name - = @snippet.file_name - .file-actions.hidden-xs - = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") - = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" - = render 'shared/snippets/blob' +%article.file-holder.snippet-file-content + .file-title + = blob_icon 0, @snippet.file_name + = @snippet.file_name + .file-actions + = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") + = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" + = render 'shared/snippets/blob' - %div#notes= render "projects/notes/notes_with_form" +%div#notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml index 6c43f822db4..07cee86ba4c 100644 --- a/app/views/projects/variables/_table.html.haml +++ b/app/views/projects/variables/_table.html.haml @@ -9,7 +9,7 @@ %th Value %th %tbody - - @project.variables.each do |variable| + - @project.variables.order_key_asc.each do |variable| - if variable.id? %tr %td= variable.key diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 290743feb4a..6f0a0ea36ec 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,4 +1,4 @@ -- blob = @project.repository.parse_search_result(blob) +- blob = parse_search_result(blob) .blob-result .file-holder .file-title diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index 235106c4f74..648d0bd76cb 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -1,4 +1,4 @@ -- wiki_blob = @project.repository.parse_search_result(wiki_blob) +- wiki_blob = parse_search_result(wiki_blob) .blob-result .file-holder .file-title diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml index 107ad19177c..add4536a0a2 100644 --- a/app/views/shared/_visibility_level.html.haml +++ b/app/views/shared/_visibility_level.html.haml @@ -1,7 +1,7 @@ .form-group.project-visibility-level-holder = f.label :visibility_level, class: 'control-label' do Visibility Level - = link_to "(?)", help_page_path("public_access/public_access") + = link_to icon('question-circle'), help_page_path("public_access/public_access") .col-sm-10 - if can_change_visibility_level = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml new file mode 100644 index 00000000000..60353aee7f1 --- /dev/null +++ b/app/views/shared/builds/_tabs.html.haml @@ -0,0 +1,24 @@ +%ul.nav-links + %li{ class: ('active' if scope.nil?) } + = link_to build_path_proc.call(nil) do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(all_builds.count(:id)) + + %li{ class: ('active' if scope == 'pending') } + = link_to build_path_proc.call('pending') do + Pending + %span.badge + = number_with_delimiter(all_builds.pending.count(:id)) + + %li{ class: ('active' if scope == 'running') } + = link_to build_path_proc.call('running') do + Running + %span.badge + = number_with_delimiter(all_builds.running.count(:id)) + + %li{ class: ('active' if scope == 'finished') } + = link_to build_path_proc.call('finished') do + Finished + %span.badge + = number_with_delimiter(all_builds.finished.count(:id)) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 3856a4917b4..04373684ee9 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -19,7 +19,7 @@ = dropdown_tag(title, options: { toggle_class: 'js-issuable-selector', title: title, filter: true, placeholder: 'Filter', footer_content: true, - data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: @project.path, namespace_path: @project.namespace.path } } ) do + data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do %ul.dropdown-footer-list %li %a.reset-template diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index af753496260..7ae4211ddfd 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -6,12 +6,13 @@ %strong.item-title Snippet #{@snippet.to_reference} %span.creator - created by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title")} + authored = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago') - if @snippet.updated_at != @snippet.created_at %span = icon('edit', title: 'edited') = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago') + by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")} .snippet-actions - if @snippet.project_id? @@ -19,6 +20,5 @@ - else = render "snippets/actions" -.content-block.second-block - %h2.snippet-title.prepend-top-0.append-bottom-0 - = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author +%h2.snippet-title.prepend-top-0.append-bottom-0 + = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index c96dfefe17f..ea17bec8677 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -3,19 +3,30 @@ .title = link_to reliable_snippet_path(snippet) do - = truncate(snippet.title, length: 60) + = snippet.title - if snippet.private? - %span.label.label-gray + %span.label.label-gray.hidden-xs = icon('lock') private - %span.monospace.pull-right + %span.monospace.pull-right.hidden-xs = snippet.file_name - %small.pull-right.cgray + %ul.controls.visible-xs + %li + - note_count = snippet.notes.user.count + = link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do + = icon('comments') + = note_count + %li + %span.sr-only + = visibility_level_label(snippet.visibility_level) + = visibility_level_icon(snippet.visibility_level, fw: false) + + %small.pull-right.cgray.hidden-xs - if snippet.project_id? = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) - .snippet-info + .snippet-info.hidden-xs = link_to user_snippets_path(snippet.author) do = snippet.author_name authored #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 160c6cd84da..fdaca199218 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -2,12 +2,12 @@ - if current_user = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do New Snippet - - if can?(current_user, :update_personal_snippet, @snippet) - = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do - Edit - if can?(current_user, :admin_personal_snippet, @snippet) = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do Delete + - if can?(current_user, :update_personal_snippet, @snippet) + = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do + Edit - if current_user .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } @@ -18,11 +18,11 @@ %li = link_to new_snippet_path, title: "New Snippet" do New Snippet - - if can?(current_user, :update_personal_snippet, @snippet) - %li - = link_to edit_snippet_path(@snippet) do - Edit - if can?(current_user, :admin_personal_snippet, @snippet) %li = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do Delete + - if can?(current_user, :update_personal_snippet, @snippet) + %li + = link_to edit_snippet_path(@snippet) do + Edit diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index ed3992650d4..fa403da8f79 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -1,13 +1,12 @@ - page_title @snippet.title, "Snippets" -.snippet-holder - = render 'shared/snippets/header' += render 'shared/snippets/header' - %article.file-holder.file-holder-no-border.snippet-file-content - .file-title.file-title-clear - = blob_icon 0, @snippet.file_name - = @snippet.file_name - .file-actions.hidden-xs - = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") - = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" - = render 'shared/snippets/blob' +%article.file-holder.snippet-file-content + .file-title + = blob_icon 0, @snippet.file_name + = @snippet.file_name + .file-actions + = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") + = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" + = render 'shared/snippets/blob' diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 52522e099e7..be22085b0df 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -68,7 +68,8 @@ if Gitlab::Metrics.enabled? ['app', 'mailers', 'emails'] => ['app', 'mailers'], ['app', 'services', '**'] => ['app', 'services'], ['lib', 'gitlab', 'diff'] => ['lib'], - ['lib', 'gitlab', 'email', 'message'] => ['lib'] + ['lib', 'gitlab', 'email', 'message'] => ['lib'], + ['lib', 'gitlab', 'checks'] => ['lib'] } paths_to_instrument.each do |(path, prefix)| diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index f498732feca..5e3e4c966cb 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -13,9 +13,5 @@ Mime::Type.register "video/mp4", :mp4, [], [:m4v, :mov] Mime::Type.register "video/webm", :webm Mime::Type.register "video/ogg", :ogv -middlewares = Gitlab::Application.config.middleware -middlewares.swap(ActionDispatch::ParamsParser, ActionDispatch::ParamsParser, { - Mime::Type.lookup('application/vnd.git-lfs+json') => lambda do |body| - ActiveSupport::JSON.decode(body) - end -}) +Mime::Type.unregister :json +Mime::Type.register 'application/json', :json, %w(application/vnd.git-lfs+json application/json) diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 49e6e2361b1..650b410595c 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines BUILDS = [ { name: 'build:linux', stage: 'build', status: :success }, { name: 'build:osx', stage: 'build', status: :success }, - { name: 'rspec:linux', stage: 'test', status: :success }, - { name: 'rspec:windows', stage: 'test', status: :success }, - { name: 'rspec:windows', stage: 'test', status: :success }, + { name: 'rspec:linux 0 3', stage: 'test', status: :success }, + { name: 'rspec:linux 1 3', stage: 'test', status: :success }, + { name: 'rspec:linux 2 3', stage: 'test', status: :success }, + { name: 'rspec:windows 0 3', stage: 'test', status: :success }, + { name: 'rspec:windows 1 3', stage: 'test', status: :success }, + { name: 'rspec:windows 2 3', stage: 'test', status: :success }, + { name: 'rspec:windows 2 3', stage: 'test', status: :success }, { name: 'rspec:osx', stage: 'test', status_event: :success }, { name: 'spinach:linux', stage: 'test', status: :success }, { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true}, diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb index 84463727b3b..e8de7ccf3db 100644 --- a/db/migrate/20140502125220_migrate_repo_size.rb +++ b/db/migrate/20140502125220_migrate_repo_size.rb @@ -1,12 +1,15 @@ # rubocop:disable all class MigrateRepoSize < ActiveRecord::Migration + DOWNTIME = false + def up project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id') project_data.each do |project| id = project['id'] namespace_path = project['namespace_path'] || '' - path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git') + repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default + path = File.join(repos_path, namespace_path, project['project_path'] + '.git') begin repo = Gitlab::Git::Repository.new(path) diff --git a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb index c8cbd2718ff..75a3eb15124 100644 --- a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb +++ b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb @@ -8,14 +8,28 @@ class MergeRequestDiffRemoveUniq < ActiveRecord::Migration DOWNTIME = false def up - if index_exists?(:merge_request_diffs, :merge_request_id) - remove_index :merge_request_diffs, :merge_request_id + constraint_name = 'merge_request_diffs_merge_request_id_key' + + transaction do + if index_exists?(:merge_request_diffs, :merge_request_id) + remove_index(:merge_request_diffs, :merge_request_id) + end + + # In some bizarre cases PostgreSQL might have a separate unique constraint + # that we'll need to drop. + if constraint_exists?(constraint_name) && Gitlab::Database.postgresql? + execute("ALTER TABLE merge_request_diffs DROP CONSTRAINT IF EXISTS #{constraint_name};") + end end end def down unless index_exists?(:merge_request_diffs, :merge_request_id) - add_concurrent_index :merge_request_diffs, :merge_request_id, unique: true + add_concurrent_index(:merge_request_diffs, :merge_request_id, unique: true) end end + + def constraint_exists?(name) + indexes(:merge_request_diffs).map(&:name).include?(name) + end end diff --git a/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb new file mode 100644 index 00000000000..fd413d1ca8c --- /dev/null +++ b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb @@ -0,0 +1,12 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddLfsEnabledToNamespaces < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :namespaces, :lfs_enabled, :boolean + end +end diff --git a/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb new file mode 100644 index 00000000000..18ea9d43a43 --- /dev/null +++ b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb @@ -0,0 +1,19 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveProjectsPushesSinceGc < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'This migration removes an existing column' + + disable_ddl_transaction! + + def up + remove_column :projects, :pushes_since_gc + end + + def down + add_column_with_default :projects, :pushes_since_gc, :integer, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 5c283141084..70279f372c9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160902122721) do +ActiveRecord::Schema.define(version: 20160913162434) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -650,6 +650,7 @@ ActiveRecord::Schema.define(version: 20160902122721) do t.integer "visibility_level", default: 20, null: false t.boolean "request_access_enabled", default: true, null: false t.datetime "deleted_at" + t.boolean "lfs_enabled" end add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree @@ -824,7 +825,6 @@ ActiveRecord::Schema.define(version: 20160902122721) do t.integer "build_timeout", default: 3600, null: false t.boolean "pending_delete", default: false t.boolean "public_builds", default: true, null: false - t.integer "pushes_since_gc", default: 0 t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" t.boolean "container_registry_enabled" diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index 28c4c7c86ca..c5611e2a121 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -406,7 +406,8 @@ To configure the storage driver in Omnibus: 's3' => { 'accesskey' => 's3-access-key', 'secretkey' => 's3-secret-key-for-access-key', - 'bucket' => 'your-s3-bucket' + 'bucket' => 'your-s3-bucket', + 'region' => 'your-s3-region' } } ``` @@ -428,6 +429,7 @@ storage: accesskey: 'AKIAKIAKI' secretkey: 'secret123' bucket: 'gitlab-registry-bucket-AKIAKIAKI' + region: 'your-s3-region' cache: blobdescriptor: inmemory delete: diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md index 2a71b087f19..b6d79706a84 100644 --- a/doc/api/ci/builds.md +++ b/doc/api/ci/builds.md @@ -38,6 +38,15 @@ POST /ci/api/v1/builds/register curl --request POST "https://gitlab.example.com/ci/api/v1/builds/register" --form "token=t0k3n" ``` +**Responses:** + +| Status | Data |Description | +|--------|------|---------------------------------------------------------------------------| +| `201` | yes | When a build is scheduled for a runner | +| `204` | no | When no builds are scheduled for a runner (for GitLab Runner >= `v1.3.0`) | +| `403` | no | When invalid token is used or no token is sent | +| `404` | no | When no builds are scheduled for a runner (for GitLab Runner < `v1.3.0`) **or** when the runner is set to `paused` in GitLab runner's configuration page | + ### Update details of an existing build ``` diff --git a/doc/api/groups.md b/doc/api/groups.md index a898387eaa2..3e94e1e4efe 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -288,6 +288,7 @@ Parameters: - `path` (required) - The path of the group - `description` (optional) - The group's description - `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public. +- `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group ## Transfer project to group @@ -317,6 +318,7 @@ PUT /groups/:id | `path` | string | no | The path of the group | | `description` | string | no | The description of the group | | `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. | +| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index c835ebc2d44..c40cdd55ea5 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -105,7 +105,8 @@ What is important is that each job is run independently from each other. If you want to check whether your `.gitlab-ci.yml` file is valid, there is a Lint tool under the page `/ci/lint` of your GitLab instance. You can also find -the link under **Settings > CI settings** in your project. +a "CI Lint" button to go to this page under **Pipelines > Pipelines** and +**Pipelines > Builds** in your project. For more information and a complete `.gitlab-ci.yml` syntax, please read [the documentation on .gitlab-ci.yml](../yaml/README.md). diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index c32831d3aaa..6a971c3ae87 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -34,6 +34,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built | | **CI_BUILD_REPO** | all | all | The URL to clone the Git repository | | **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that build was [triggered] | +| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that build was manually started | | **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally | | **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally | @@ -47,6 +48,8 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | +| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | **Some of the variables are only available when using runner with at least defined version.** @@ -60,6 +63,7 @@ export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/git export CI_BUILD_TAG="1.0.0" export CI_BUILD_NAME="spec:other" export CI_BUILD_STAGE="test" +export CI_BUILD_MANUAL="true" export CI_BUILD_TRIGGERED="true" export CI_BUILD_TOKEN="abcde-1234ABCD5678ef" export CI_PIPELINE_ID="1000" @@ -78,6 +82,8 @@ export CI_SERVER="yes" export CI_SERVER_NAME="GitLab" export CI_SERVER_REVISION="70606bf" export CI_SERVER_VERSION="8.9.0" +export GITLAB_USER_ID="42" +export GITLAB_USER_EMAIL="alexzander@sporer.com" ``` ### YAML-defined variables diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index c2272ab0a2b..105e2f1242a 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -137,3 +137,18 @@ end ``` Here the final value of `sleep_real_time` will be `3`, _not_ `1`. + +## Tracking Custom Events + +Besides instrumenting code GitLab Performance Monitoring also supports tracking +of custom events. This is primarily intended to be used for tracking business +metrics such as the number of Git pushes, repository imports, and so on. + +To track a custom event simply call `Gitlab::Metrics.add_event` passing it an +event name and a custom set of (optional) tags. For example: + +```ruby +Gitlab::Metrics.add_event(:user_login, email: current_user.email) +``` + +Event names should be verbs such as `push_repository` and `remove_branch`. diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 011c2b0e969..8017c36587e 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -70,7 +70,7 @@ sudo -u git -H git checkout 8-12-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v3.4.0 +sudo -u git -H git checkout v3.5.0 ``` ### 6. Update gitlab-workhorse diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 1498cb361c8..f1b75298180 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project. | Force push to protected branches [^2] | | | | | | | Remove protected branches [^2] | | | | | | -[^1]: If **Allow guest to access builds** is enabled in CI settings +[^1]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines** [^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner ## Group diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md index f79535d1542..5af9a5d049c 100644 --- a/doc/user/project/merge_requests.md +++ b/doc/user/project/merge_requests.md @@ -93,6 +93,9 @@ A merge request contains all the history from a repository, plus the additional commits added to the branch associated with the merge request. Here's a few tricks to checkout a merge request locally. +Please note that you can checkout a merge request locally even if the source +project is a fork (even a private fork) of the target project. + #### Checkout locally by adding a git alias Add the following alias to your `~/.gitconfig`: diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index 306caabf6e6..370d885d366 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -15,6 +15,7 @@ At its current state, GitHub importer can import: - the wiki pages (introduced in GitLab 8.4)
- the milestones (introduced in GitLab 8.7)
- the labels (introduced in GitLab 8.7)
+- the release note descriptions (introduced in GitLab 8.12)
With GitLab 8.7+, references to pull requests and issues are preserved.
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 5e3c9563703..dfbdd597d29 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -37,7 +37,7 @@ module API # id (required) - The ID of a project # sha (required) - The commit hash # ref (optional) - The ref - # state (required) - The state of the status. Can be: pending, running, success, error or failure + # state (required) - The state of the status. Can be: pending, running, success, failed or canceled # target_url (optional) - The target URL to associate with this status # description (optional) - A short description of the status # name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default" @@ -46,7 +46,7 @@ module API post ':id/statuses/:sha' do authorize! :create_commit_status, user_project required_attributes! [:state] - attrs = attributes_for_keys [:ref, :target_url, :description, :context, :name] + attrs = attributes_for_keys [:target_url, :description] commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit @@ -58,36 +58,38 @@ module API # the first found branch on that commit ref = params[:ref] - unless ref - branches = @project.repository.branch_names_contains(commit.sha) - not_found! 'References for commit' if branches.none? - ref = branches.first - end + ref ||= @project.repository.branch_names_contains(commit.sha).first + not_found! 'References for commit' unless ref + + name = params[:name] || params[:context] || 'default' pipeline = @project.ensure_pipeline(ref, commit.sha, current_user) - name = params[:name] || params[:context] - status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref]) - status ||= GenericCommitStatus.new(project: @project, pipeline: pipeline, user: current_user) - status.update(attrs) + status = GenericCommitStatus.running_or_pending.find_or_initialize_by( + project: @project, pipeline: pipeline, + user: current_user, name: name, ref: ref) + status.attributes = attrs - case params[:state].to_s - when 'running' - status.run - when 'success' - status.success - when 'failed' - status.drop - when 'canceled' - status.cancel - else - status.status = params[:state].to_s - end + begin + case params[:state].to_s + when 'pending' + status.enqueue! + when 'running' + status.enqueue + status.run! + when 'success' + status.success! + when 'failed' + status.drop! + when 'canceled' + status.cancel! + else + render_api_error!('invalid state', 400) + end - if status.save present status, with: Entities::CommitStatus - else - render_validation_error!(status) + rescue StateMachines::InvalidTransition => e + render_api_error!(e.message, 400) end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4f736e4ec2b..bfee4b6c752 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -86,7 +86,8 @@ module API expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) } expose :created_at, :last_activity_at - expose :shared_runners_enabled, :lfs_enabled + expose :shared_runners_enabled + expose :lfs_enabled?, as: :lfs_enabled expose :creator_id expose :namespace expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } @@ -121,6 +122,7 @@ module API class Group < Grape::Entity expose :id, :name, :path, :description, :visibility_level + expose :lfs_enabled?, as: :lfs_enabled expose :avatar_url expose :web_url end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index d2df77238d5..60ac9bdfa33 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -27,13 +27,14 @@ module API # path (required) - The path of the group # description (optional) - The description of the group # visibility_level (optional) - The visibility level of the group + # lfs_enabled (optional) - Enable/disable LFS for the projects in this group # Example Request: # POST /groups post do authorize! :create_group required_attributes! [:name, :path] - attrs = attributes_for_keys [:name, :path, :description, :visibility_level] + attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled] @group = Group.new(attrs) if @group.save @@ -51,13 +52,14 @@ module API # path (optional) - The path of the group # description (optional) - The description of the group # visibility_level (optional) - The visibility level of the group + # lfs_enabled (optional) - Enable/disable LFS for the projects in this group # Example Request: # PUT /groups/:id put ':id' do group = find_group(params[:id]) authorize! :admin_group, group - attrs = attributes_for_keys [:name, :path, :description, :visibility_level] + attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled] if ::Groups::UpdateService.new(group, current_user, attrs).execute present group, with: Entities::GroupDetail diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6a20ba95a79..150875ed4f0 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -269,6 +269,10 @@ module API render_api_error!('304 Not Modified', 304) end + def no_content! + render_api_error!('204 No Content', 204) + end + def render_validation_error!(model) if model.errors.any? render_api_error!(model.errors.messages || '400 Bad Request', 400) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 556684187d8..c9689e6f8ef 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -41,7 +41,8 @@ module API issues = current_user.issues.inc_notes_with_associations issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? - issues.reorder(issuable_order_by => issuable_sort) + issues = issues.reorder(issuable_order_by => issuable_sort) + present paginate(issues), with: Entities::Issue, current_user: current_user end end @@ -73,7 +74,11 @@ module API params[:group_id] = group.id params[:milestone_title] = params.delete(:milestone) params[:label_name] = params.delete(:labels) - params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort] + + if params[:order_by] || params[:sort] + # The Sortable concern takes 'created_desc', not 'created_at_desc' (for example) + params[:sort] = "#{issuable_order_by.sub('_at', '')}_#{issuable_sort}" + end issues = IssuesFinder.new(current_user, params).execute @@ -113,7 +118,8 @@ module API issues = filter_issues_milestone(issues, params[:milestone]) end - issues.reorder(issuable_order_by => issuable_sort) + issues = issues.reorder(issuable_order_by => issuable_sort) + present paginate(issues), with: Entities::Issue, current_user: current_user end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 4033f597859..644d836ed0b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -428,18 +428,9 @@ module API # Example Request: # GET /projects/search/:query get "/search/:query" do - ids = current_user.authorized_projects.map(&:id) - visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] - projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") - sort = params[:sort] == 'desc' ? 'desc' : 'asc' - - projects = case params["order_by"] - when 'id' then projects.order("id #{sort}") - when 'name' then projects.order("name #{sort}") - when 'created_at' then projects.order("created_at #{sort}") - when 'last_activity_at' then projects.order("last_activity_at #{sort}") - else projects - end + search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + projects = search_service.objects('projects', params[:page]) + projects = projects.reorder(project_order_by => project_sort) present paginate(projects), with: Entities::Project end diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb index 2e2c8da311e..e7a1ec8457d 100644 --- a/lib/banzai/filter/wiki_link_filter/rewriter.rb +++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb @@ -31,6 +31,7 @@ module Banzai def apply_relative_link_rules! if @uri.relative? && @uri.path.present? link = ::File.join(@wiki_base_path, @uri.path) + link = "#{link}##{@uri.fragment}" if @uri.fragment @uri = Addressable::URI.parse(link) end end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 54db63d4628..59f85416ee5 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -27,7 +27,7 @@ module Ci else Gitlab::Metrics.add_event(:build_not_found) - not_found! + build_not_found! end end diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index bcabf7a21b2..ba80c89df78 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -32,6 +32,14 @@ module Ci end end + def build_not_found! + if headers['User-Agent'].match(/gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /) + no_content! + else + not_found! + end + end + def current_runner @runner ||= Runner.find_by_token(params[:token].to_s) end diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 839a4fa30d5..c412249a01e 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -195,7 +195,7 @@ module Gitlab # Create (if necessary) and link the secret token file def generate_and_link_secret_token secret_file = Gitlab.config.gitlab_shell.secret_file - unless File.exist? secret_file + unless File.size?(secret_file) # Generate a new token of 16 random hexadecimal characters and store it in secret_file. token = SecureRandom.hex(16) File.write(secret_file, token) diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 4b32eb966aa..cb1065223d4 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -23,6 +23,7 @@ module Gitlab protected def protected_branch_checks + return unless @branch_name return unless project.protected_branch?(@branch_name) if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches) diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 9b681e636c7..bd90d24a2ec 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -17,11 +17,13 @@ module Gitlab def trigger(gl_id, oldrev, newrev, ref) return [true, nil] unless exists? - case name - when "pre-receive", "post-receive" - call_receive_hook(gl_id, oldrev, newrev, ref) - when "update" - call_update_hook(gl_id, oldrev, newrev, ref) + Bundler.with_clean_env do + case name + when "pre-receive", "post-receive" + call_receive_hook(gl_id, oldrev, newrev, ref) + when "update" + call_update_hook(gl_id, oldrev, newrev, ref) + end end end diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb index d546e102c63..8cacf4f4925 100644 --- a/lib/gitlab/github_import/base_formatter.rb +++ b/lib/gitlab/github_import/base_formatter.rb @@ -20,6 +20,11 @@ module Gitlab find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s). try(:id) end + + def gitlab_author_id + return @gitlab_author_id if defined?(@gitlab_author_id) + @gitlab_author_id = gitlab_user_id(raw_data.user.id) + end end end end diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb index 1c7c1a73c77..2bddcde2b7c 100644 --- a/lib/gitlab/github_import/comment_formatter.rb +++ b/lib/gitlab/github_import/comment_formatter.rb @@ -21,7 +21,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -52,7 +52,11 @@ module Gitlab end def note - formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def type diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 0388c58f811..d35ee2a1c65 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -24,6 +24,7 @@ module Gitlab import_issues import_pull_requests import_wiki + import_releases handle_errors true @@ -177,6 +178,18 @@ module Gitlab errors << { type: :wiki, errors: e.message } end end + + def import_releases + releases = client.releases(repo, per_page: 100) + releases.each do |raw| + begin + gh_release = ReleaseFormatter.new(project, raw) + gh_release.create! if gh_release.valid? + rescue => e + errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end + end + end end end end diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb index ad4f1d8ae99..77621de9f4c 100644 --- a/lib/gitlab/github_import/issue_formatter.rb +++ b/lib/gitlab/github_import/issue_formatter.rb @@ -49,7 +49,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -57,7 +57,11 @@ module Gitlab end def description - @formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def milestone diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index 87e031b27f8..1408683100f 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -77,7 +77,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -85,7 +85,11 @@ module Gitlab end def description - formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def milestone diff --git a/lib/gitlab/github_import/release_formatter.rb b/lib/gitlab/github_import/release_formatter.rb new file mode 100644 index 00000000000..73d643b00ad --- /dev/null +++ b/lib/gitlab/github_import/release_formatter.rb @@ -0,0 +1,23 @@ +module Gitlab + module GithubImport + class ReleaseFormatter < BaseFormatter + def attributes + { + project: project, + tag: raw_data.tag_name, + description: raw_data.body, + created_at: raw_data.created_at, + updated_at: raw_data.created_at + } + end + + def klass + Release + end + + def valid? + !raw_data.draft + end + end + end +end diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb index 6d9379acf25..d1e33ea8678 100644 --- a/lib/gitlab/import_export/repo_restorer.rb +++ b/lib/gitlab/import_export/repo_restorer.rb @@ -22,10 +22,6 @@ module Gitlab private - def repos_path - Gitlab.config.gitlab_shell.repos_path - end - def path_to_repo @project.repository.path_to_repo end diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index a0fd41161a5..cc74bb29087 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -18,18 +18,18 @@ module Gitlab FileUtils.mkdir_p(path) end - @cmd_output = "" - @cmd_status = 0 + cmd_output = "" + cmd_status = 0 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| yield(stdin) if block_given? stdin.close - @cmd_output << stdout.read - @cmd_output << stderr.read - @cmd_status = wait_thr.value.exitstatus + cmd_output << stdout.read + cmd_output << stderr.read + cmd_status = wait_thr.value.exitstatus end - [@cmd_output, @cmd_status] + [cmd_output, cmd_status] end end end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index efe4aeb399d..60aae541d46 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -102,7 +102,7 @@ module Gitlab def secret @secret ||= begin - bytes = Base64.strict_decode64(File.read(secret_path)) + bytes = Base64.strict_decode64(File.read(secret_path).chomp) raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH bytes end diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake new file mode 100644 index 00000000000..609dfaa48e3 --- /dev/null +++ b/lib/tasks/haml-lint.rake @@ -0,0 +1,5 @@ +unless Rails.env.production? + require 'haml_lint/rake_task' + + HamlLint::RakeTask.new +end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index ffe0641ddd7..b0f740f48f7 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -181,6 +181,25 @@ describe ProjectsController do expect(response).to have_http_status(302) expect(response).to redirect_to(dashboard_projects_path) end + + context "when the project is forked" do + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } + let(:merge_request) do + create(:merge_request, + source_project: fork_project, + target_project: project) + end + + it "closes all related merge requests" do + project.merge_requests << merge_request + sign_in(admin) + + delete :destroy, namespace_id: fork_project.namespace.path, id: fork_project.path + + expect(merge_request.reload.state).to eq('closed') + end + end end describe "POST #toggle_star" do diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index 5b645fab32e..45eaebb2576 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -30,5 +30,9 @@ FactoryGirl.define do trait :shared do is_shared true end + + trait :inactive do + active false + end end end diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 2c0a2dd94ca..2b4670be468 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -1,4 +1,8 @@ FactoryGirl.define do + sequence :issue_created_at do |n| + 4.hours.ago + ( 2 * n ).seconds + end + factory :issue do title author diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index c6c2e2095df..e51586d32ec 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' describe 'Issue Boards', feature: true, js: true do include WaitForAjax + include WaitForVueResource let(:project) { create(:empty_project, :public) } let(:user) { create(:user) } @@ -187,13 +188,13 @@ describe 'Issue Boards', feature: true, js: true do expect(page).to have_content('Showing 20 of 56 issues') evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(page).to have_selector('.card', count: 40) expect(page).to have_content('Showing 40 of 56 issues') evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(page).to have_selector('.card', count: 56) expect(page).to have_content('Showing all issues') @@ -372,7 +373,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-author' do click_link(user2.name) end - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(find('.js-author-search')).to have_content(user2.name) end @@ -398,7 +399,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-assignee' do click_link(user.name) end - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(find('.js-assignee-search')).to have_content(user.name) end @@ -424,7 +425,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.milestone-filter' do click_link(milestone.title) end - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(find('.js-milestone-select')).to have_content(milestone.title) end @@ -449,7 +450,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-labels' do click_link(testing.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource find('.dropdown-menu-close').click end end @@ -478,7 +479,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-labels' do click_link(testing.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource find('.dropdown-menu-close').click end end @@ -509,9 +510,9 @@ describe 'Issue Boards', feature: true, js: true do page.within(find('.dropdown-menu-labels')) do click_link(testing.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource click_link(bug.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource find('.dropdown-menu-close').click end end @@ -536,7 +537,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-labels' do click_link("No Label") - wait_for_vue_resource(spinner: false) + wait_for_vue_resource find('.dropdown-menu-close').click end end @@ -559,7 +560,7 @@ describe 'Issue Boards', feature: true, js: true do expect(page).to have_selector('.card', count: 6) expect(find('.card', match: :first)).to have_content(bug.title) click_button(bug.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource end wait_for_vue_resource @@ -584,7 +585,7 @@ describe 'Issue Boards', feature: true, js: true do page.within(find('.card', match: :first)) do click_button(bug.title) end - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(page).to have_selector('.card', count: 1) end @@ -647,14 +648,4 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource end - - def wait_for_vue_resource(spinner: true) - Timeout.timeout(Capybara.default_max_wait_time) do - loop until page.evaluate_script('Vue.activeResources').zero? - end - - if spinner - expect(find('.boards-list')).not_to have_selector('.fa-spinner') - end - end end diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb new file mode 100644 index 00000000000..7ef68e9eb8d --- /dev/null +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +describe 'Issue Boards shortcut', feature: true, js: true do + include WaitForVueResource + + let(:project) { create(:empty_project) } + + before do + project.create_board + project.board.lists.create(list_type: :backlog) + project.board.lists.create(list_type: :done) + + login_as :admin + + visit namespace_project_path(project.namespace, project) + end + + it 'takes user to issue board index' do + find('body').native.send_keys('gl') + expect(page).to have_selector('.boards-list') + + wait_for_vue_resource + end +end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index d744d0eb6af..22359c8f938 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -144,7 +144,7 @@ describe 'Issues', feature: true do visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id) expect(page).to have_content 'foobar' - expect(page.all('.issue-no-comments').first.text).to eq "0" + expect(page.all('.no-comments').first.text).to eq "0" end end diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb new file mode 100644 index 00000000000..3b20d38c520 --- /dev/null +++ b/spec/features/profiles/keys_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +describe 'Profile > SSH Keys', feature: true do + let(:user) { create(:user) } + + before do + login_as(user) + visit profile_keys_path + end + + describe 'User adds an SSH key' do + it 'auto-populates the title', js: true do + fill_in('Key', with: attributes_for(:key).fetch(:key)) + + expect(find_field('Title').value).to eq 'dummy@gitlab.com' + end + end +end diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 4a83740621a..f76c4fe8b57 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do context 'user creates an issue using templates' do let(:template_content) { 'this is a test "bug" template' } + let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) } let(:issue) { create(:issue, author: user, assignee: user, project: project) } background do project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) + project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false) visit edit_namespace_project_issue_path project.namespace, project, issue fill_in :'issue[title]', with: 'test issue title' end @@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do preview_template save_changes end + + it 'updates height of markdown textarea' do + start_height = page.evaluate_script('$(".markdown-area").outerHeight()') + + select_template 'test' + wait_for_ajax + + end_height = page.evaluate_script('$(".markdown-area").outerHeight()') + + expect(end_height).not_to eq(start_height) + end end context 'user creates a merge request using templates' do @@ -51,7 +64,7 @@ feature 'issuable templates', feature: true, js: true do let(:template_content) { 'this is a test "feature-proposal" template' } let(:fork_user) { create(:user) } let(:fork_project) { create(:project, :public) } - let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project) } + let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) } background do logout @@ -59,16 +72,20 @@ feature 'issuable templates', feature: true, js: true do fork_project.team << [fork_user, :master] create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project) login_as fork_user - fork_project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false) - visit edit_namespace_project_merge_request_path fork_project.namespace, fork_project, merge_request + project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false) + visit edit_namespace_project_merge_request_path project.namespace, project, merge_request fill_in :'merge_request[title]', with: 'test merge request title' end - scenario 'user selects "feature-proposal" template' do - select_template 'feature-proposal' - wait_for_ajax - preview_template - save_changes + context 'feature proposal template' do + context 'template exists in target project' do + scenario 'user selects template' do + select_template 'feature-proposal' + wait_for_ajax + preview_template + save_changes + end + end end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index e00d85904d5..2242cb6236a 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -57,7 +57,7 @@ feature 'Project', feature: true do describe 'removal', js: true do let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace, name: 'project1') } before do login_with(user) @@ -65,8 +65,12 @@ feature 'Project', feature: true do visit edit_namespace_project_path(project.namespace, project) end - it 'removes project' do + it 'removes a project' do expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1) + expect(page).to have_content "Project 'project1' will be deleted." + expect(Project.all.count).to be_zero + expect(project.issues).to be_empty + expect(project.merge_requests).to be_empty end end diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb index 83cf306437d..b9e66243d84 100644 --- a/spec/features/todos/todos_filtering_spec.rb +++ b/spec/features/todos/todos_filtering_spec.rb @@ -29,8 +29,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do fill_in 'Search projects', with: project_1.name_with_namespace click_link project_1.name_with_namespace end + wait_for_ajax - expect('.prepend-top-default').not_to have_content project_2.name_with_namespace + + expect(page).to have_content project_1.name_with_namespace + expect(page).not_to have_content project_2.name_with_namespace end it 'filters by author' do @@ -39,8 +42,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do fill_in 'Search authors', with: user_1.name click_link user_1.name end + wait_for_ajax - expect('.prepend-top-default').not_to have_content user_2.name + + expect(find('.todos-list')).to have_content user_1.name + expect(find('.todos-list')).not_to have_content user_2.name end it 'filters by type' do @@ -48,8 +54,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do within '.dropdown-menu-type' do click_link 'Issue' end + wait_for_ajax - expect('.prepend-top-default').not_to have_content ' merge request !' + + expect(find('.todos-list')).to have_content issue.to_reference + expect(find('.todos-list')).not_to have_content merge_request.to_reference end it 'filters by action' do @@ -57,7 +66,10 @@ describe 'Dashboard > User filters todos', feature: true, js: true do within '.dropdown-menu-action' do click_link 'Assigned' end + wait_for_ajax - expect('.prepend-top-default').not_to have_content ' mentioned ' + + expect(find('.todos-list')).to have_content ' assigned you ' + expect(find('.todos-list')).not_to have_content ' mentioned ' end end diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb index 356a8d668b0..f00abd82fea 100644 --- a/spec/features/users/snippets_spec.rb +++ b/spec/features/users/snippets_spec.rb @@ -19,7 +19,10 @@ describe 'Snippets tab on a user profile', feature: true, js: true do end context 'clicking on the link to the second page' do - before { click_link('2') } + before do + click_link('2') + wait_for_ajax + end it 'shows the remaining snippets' do expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5) diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 0807534720a..233d00534e5 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -18,4 +18,67 @@ describe GroupsHelper do expect(group_icon(group.path)).to match('group_avatar.png') end end + + describe 'group_lfs_status' do + let(:group) { create(:group) } + let!(:project) { create(:empty_project, namespace_id: group.id) } + + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + context 'only one project in group' do + before do + group.update_attribute(:lfs_enabled, true) + end + + it 'returns all projects as enabled' do + expect(group_lfs_status(group)).to include('Enabled for all projects') + end + + it 'returns all projects as disabled' do + project.update_attribute(:lfs_enabled, false) + + expect(group_lfs_status(group)).to include('Enabled for 0 out of 1 project') + end + end + + context 'more than one project in group' do + before do + create(:empty_project, namespace_id: group.id) + end + + context 'LFS enabled in group' do + before do + group.update_attribute(:lfs_enabled, true) + end + + it 'returns both projects as enabled' do + expect(group_lfs_status(group)).to include('Enabled for all projects') + end + + it 'returns only one as enabled' do + project.update_attribute(:lfs_enabled, false) + + expect(group_lfs_status(group)).to include('Enabled for 1 out of 2 projects') + end + end + + context 'LFS disabled in group' do + before do + group.update_attribute(:lfs_enabled, false) + end + + it 'returns both projects as disabled' do + expect(group_lfs_status(group)).to include('Disabled for all projects') + end + + it 'returns only one as disabled' do + project.update_attribute(:lfs_enabled, true) + + expect(group_lfs_status(group)).to include('Disabled for 1 out of 2 projects') + end + end + end + end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 4b2ca3514f8..c5b5aa8c445 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -6,6 +6,38 @@ describe SearchHelper do str end + describe 'parsing result' do + let(:project) { create(:project) } + let(:repository) { project.repository } + let(:results) { repository.search_files('feature', 'master') } + let(:search_result) { results.first } + + subject { helper.parse_search_result(search_result) } + + it "returns a valid OpenStruct object" do + is_expected.to be_an OpenStruct + expect(subject.filename).to eq('CHANGELOG') + expect(subject.basename).to eq('CHANGELOG') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(186) + expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") + end + + context "when filename has extension" do + let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" } + + it { expect(subject.filename).to eq('CONTRIBUTE.md') } + it { expect(subject.basename).to eq('CONTRIBUTE') } + end + + context "when file under directory" do + let(:search_result) { "master:a/b/c.md:5:a b c\n" } + + it { expect(subject.filename).to eq('a/b/c.md') } + it { expect(subject.basename).to eq('a/b/c') } + end + end + describe 'search_autocomplete_source' do context "with no current user" do before do diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 51c89ac4889..ac9bde6baf1 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -127,6 +127,13 @@ describe Banzai::Pipeline::WikiPipeline do expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"") end + + it 'rewrites links with anchor' do + markdown = '[Link to Header](start-page#title)' + output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) + + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/start-page#title\"") + end end describe "when creating root links" do diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb index 9ae02a6c45f..c520a9c53ad 100644 --- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb @@ -73,6 +73,12 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github') expect(comment.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns note without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(comment.attributes.fetch(:note)).to eq("I'm having a problem with this.") + end end end end diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index 7df288f619f..553c849c9b4 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -98,6 +98,30 @@ describe Gitlab::GithubImport::Importer, lib: true do ) end + let(:release1) do + double( + tag_name: 'v1.0.0', + name: 'First release', + body: 'Release v1.0.0', + draft: false, + created_at: created_at, + updated_at: updated_at, + url: 'https://api.github.com/repos/octocat/Hello-World/releases/1' + ) + end + + let(:release2) do + double( + tag_name: 'v2.0.0', + name: 'Second release', + body: nil, + draft: false, + created_at: created_at, + updated_at: updated_at, + url: 'https://api.github.com/repos/octocat/Hello-World/releases/2' + ) + 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) @@ -106,6 +130,7 @@ describe Gitlab::GithubImport::Importer, lib: true do 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(Octokit::Client).to receive(:releases).and_return([release1, release2]) allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error) end @@ -127,8 +152,9 @@ describe Gitlab::GithubImport::Importer, lib: true do { 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" } - ] + { type: :wiki, errors: "Gitlab::Shell::Error" }, + { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" } + ] } described_class.new(project).execute diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb index d60c4111e99..c2f1f6b91a1 100644 --- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb @@ -109,6 +109,12 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do expect(issue.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns description without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(issue.attributes.fetch(:description)).to eq("I'm having a problem with this.") + 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 edfc6ad81c6..302f0fc0623 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -140,6 +140,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns description without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(pull_request.attributes.fetch(:description)).to eq('Please pull these awesome changes') + end end context 'when it has a milestone' do diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb new file mode 100644 index 00000000000..793128c6ab9 --- /dev/null +++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::GithubImport::ReleaseFormatter, lib: true do + let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } + let(:octocat) { double(id: 123456, login: 'octocat') } + let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } + + let(:base_data) do + { + tag_name: 'v1.0.0', + name: 'First release', + draft: false, + created_at: created_at, + published_at: created_at, + body: 'Release v1.0.0' + } + end + + subject(:release) { described_class.new(project, raw_data) } + + describe '#attributes' do + let(:raw_data) { double(base_data) } + + it 'returns formatted attributes' do + expected = { + project: project, + tag: 'v1.0.0', + description: 'Release v1.0.0', + created_at: created_at, + updated_at: created_at + } + + expect(release.attributes).to eq(expected) + end + end + + describe '#valid' do + context 'when release is not a draft' do + let(:raw_data) { double(base_data) } + + it 'returns true' do + expect(release.valid?).to eq true + end + end + + context 'when release is draft' do + let(:raw_data) { double(base_data.merge(draft: true)) } + + it 'returns false' do + expect(release.valid?).to eq false + end + end + end +end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 395192149a9..6c7fa7e7c15 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -30,6 +30,11 @@ describe Gitlab::Workhorse, lib: true do expect(subject.encoding).to eq(Encoding::ASCII_8BIT) end + it 'accepts a trailing newline' do + open(described_class.secret_path, 'a') { |f| f.write "\n" } + expect(subject.length).to eq(32) + end + it 'raises an exception if the secret file cannot be read' do File.delete(described_class.secret_path) expect { subject }.to raise_exception(Errno::ENOENT) diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index cee20234e1f..03d02b4d382 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require 'rails_helper' describe Blob do @@ -7,6 +8,25 @@ describe Blob do end end + describe '#data' do + context 'using a binary blob' do + it 'returns the data as-is' do + data = "\n\xFF\xB9\xC3" + blob = described_class.new(double(binary?: true, data: data)) + + expect(blob.data).to eq(data) + end + end + + context 'using a text blob' do + it 'converts the data to UTF-8' do + blob = described_class.new(double(binary?: false, data: "\n\xFF\xB9\xC3")) + + expect(blob.data).to eq("\n���") + end + end + end + describe '#svg?' do it 'is falsey when not text' do git_blob = double(text?: false) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index c45c2635cf4..8eab4281bc7 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -231,6 +231,34 @@ describe Ci::Build, models: true do it { is_expected.to eq(predefined_variables) } end + context 'when build has user' do + let(:user) { create(:user, username: 'starter') } + let(:user_variables) do + [ + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } + ] + end + + before do + build.update_attributes(user: user) + end + + it { user_variables.each { |v| is_expected.to include(v) } } + end + + context 'when build started manually' do + before do + build.update_attributes(when: :manual) + end + + let(:manual_variable) do + { key: 'CI_BUILD_MANUAL', value: 'true', public: true } + end + + it { is_expected.to include(manual_variable) } + end + context 'when build is for tag' do let(:tag_variable) do { key: 'CI_BUILD_TAG', value: 'master', public: true } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index fbf945c757c..f1857f846dc 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -373,8 +373,8 @@ describe Ci::Pipeline, models: true do end describe '#execute_hooks' do - let!(:build_a) { create_build('a') } - let!(:build_b) { create_build('b') } + let!(:build_a) { create_build('a', 0) } + let!(:build_b) { create_build('b', 1) } let!(:hook) do create(:project_hook, project: project, pipeline_events: enabled) @@ -398,7 +398,7 @@ describe Ci::Pipeline, models: true do build_b.enqueue end - it 'receive a pending event once' do + it 'receives a pending event once' do expect(WebMock).to have_requested_pipeline_hook('pending').once end end @@ -411,7 +411,7 @@ describe Ci::Pipeline, models: true do build_b.run end - it 'receive a running event once' do + it 'receives a running event once' do expect(WebMock).to have_requested_pipeline_hook('running').once end end @@ -422,11 +422,21 @@ describe Ci::Pipeline, models: true do build_b.success end - it 'receive a success event once' do + it 'receives a success event once' do expect(WebMock).to have_requested_pipeline_hook('success').once end end + context 'when stage one failed' do + before do + build_a.drop + end + + it 'receives a failed event once' do + expect(WebMock).to have_requested_pipeline_hook('failed').once + end + end + def have_requested_pipeline_hook(status) have_requested(:post, hook.url).with do |req| json_body = JSON.parse(req.body) @@ -450,8 +460,12 @@ describe Ci::Pipeline, models: true do end end - def create_build(name) - create(:ci_build, :created, pipeline: pipeline, name: name) + def create_build(name, stage_idx) + create(:ci_build, + :created, + pipeline: pipeline, + name: name, + stage_idx: stage_idx) end end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index fcfa3138ce5..2f1baff5d66 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -40,7 +40,7 @@ describe CommitStatus, models: true do it { is_expected.to be_falsey } end - %w(running success failed).each do |status| + %w[running success failed].each do |status| context "if commit status is #{status}" do before { commit_status.status = status } @@ -48,7 +48,7 @@ describe CommitStatus, models: true do end end - %w(pending canceled).each do |status| + %w[pending canceled].each do |status| context "if commit status is #{status}" do before { commit_status.status = status } @@ -60,7 +60,7 @@ describe CommitStatus, models: true do describe '#active?' do subject { commit_status.active? } - %w(pending running).each do |state| + %w[pending running].each do |state| context "if commit_status.status is #{state}" do before { commit_status.status = state } @@ -68,7 +68,7 @@ describe CommitStatus, models: true do end end - %w(success failed canceled).each do |state| + %w[success failed canceled].each do |state| context "if commit_status.status is #{state}" do before { commit_status.status = state } @@ -80,7 +80,7 @@ describe CommitStatus, models: true do describe '#complete?' do subject { commit_status.complete? } - %w(success failed canceled).each do |state| + %w[success failed canceled].each do |state| context "if commit_status.status is #{state}" do before { commit_status.status = state } @@ -88,7 +88,7 @@ describe CommitStatus, models: true do end end - %w(pending running).each do |state| + %w[pending running].each do |state| context "if commit_status.status is #{state}" do before { commit_status.status = state } @@ -187,7 +187,7 @@ describe CommitStatus, models: true do subject { CommitStatus.where(pipeline: pipeline).stages } it 'returns ordered list of stages' do - is_expected.to eq(%w(build test deploy)) + is_expected.to eq(%w[build test deploy]) end end @@ -223,4 +223,33 @@ describe CommitStatus, models: true do expect(commit_status.commit).to eq project.commit end end + + describe '#group_name' do + subject { commit_status.group_name } + + tests = { + 'rspec:windows' => 'rspec:windows', + 'rspec:windows 0' => 'rspec:windows 0', + 'rspec:windows 0 test' => 'rspec:windows 0 test', + 'rspec:windows 0 1' => 'rspec:windows', + 'rspec:windows 0 1 name' => 'rspec:windows name', + 'rspec:windows 0/1' => 'rspec:windows', + 'rspec:windows 0/1 name' => 'rspec:windows name', + 'rspec:windows 0:1' => 'rspec:windows', + 'rspec:windows 0:1 name' => 'rspec:windows name', + 'rspec:windows 10000 20000' => 'rspec:windows', + 'rspec:windows 0 : / 1' => 'rspec:windows', + 'rspec:windows 0 : / 1 name' => 'rspec:windows name', + '0 1 name ruby' => 'name ruby', + '0 :/ 1 name ruby' => 'name ruby' + } + + tests.each do |name, group_name| + it "'#{name}' puts in '#{group_name}'" do + commit_status.name = name + + is_expected.to eq(group_name) + end + end + end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index ea4b59c26b1..0b3ef9b98fd 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -187,6 +187,52 @@ describe Group, models: true do it { expect(group.has_master?(@members[:requester])).to be_falsey } end + describe '#lfs_enabled?' do + context 'LFS enabled globally' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + it 'returns true when nothing is set' do + expect(group.lfs_enabled?).to be_truthy + end + + it 'returns false when set to false' do + group.update_attribute(:lfs_enabled, false) + + expect(group.lfs_enabled?).to be_falsey + end + + it 'returns true when set to true' do + group.update_attribute(:lfs_enabled, true) + + expect(group.lfs_enabled?).to be_truthy + end + end + + context 'LFS disabled globally' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + end + + it 'returns false when nothing is set' do + expect(group.lfs_enabled?).to be_falsey + end + + it 'returns false when set to false' do + group.update_attribute(:lfs_enabled, false) + + expect(group.lfs_enabled?).to be_falsey + end + + it 'returns false when set to true' do + group.update_attribute(:lfs_enabled, true) + + expect(group.lfs_enabled?).to be_falsey + end + end + end + describe '#owners' do let(:owner) { create(:user) } let(:developer) { create(:user) } diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index fef90d9b5cb..0b1634f654a 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -57,7 +57,7 @@ describe Member, models: true do describe 'Scopes & finders' do before do - project = create(:project) + project = create(:empty_project) group = create(:group) @owner_user = create(:user).tap { |u| group.add_owner(u) } @owner = group.members.find_by(user_id: @owner_user.id) @@ -65,6 +65,15 @@ describe Member, models: true do @master_user = create(:user).tap { |u| project.team << [u, :master] } @master = project.members.find_by(user_id: @master_user.id) + @blocked_user = create(:user).tap do |u| + project.team << [u, :master] + project.team << [u, :developer] + + u.block! + end + @blocked_master = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::MASTER) + @blocked_developer = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::DEVELOPER) + Member.add_user( project.members, 'toto1@example.com', @@ -73,7 +82,7 @@ describe Member, models: true do ) @invited_member = project.members.invite.find_by_invite_email('toto1@example.com') - accepted_invite_user = build(:user) + accepted_invite_user = build(:user, state: :active) Member.add_user( project.members, 'toto2@example.com', @@ -91,7 +100,7 @@ describe Member, models: true do describe '.access_for_user_ids' do it 'returns the right access levels' do - users = [@owner_user.id, @master_user.id] + users = [@owner_user.id, @master_user.id, @blocked_user.id] expected = { @owner_user.id => Gitlab::Access::OWNER, @master_user.id => Gitlab::Access::MASTER @@ -125,6 +134,19 @@ describe Member, models: true do it { expect(described_class.request).not_to include @accepted_request_member } end + describe '.developers' do + subject { described_class.developers.to_a } + + it { is_expected.not_to include @owner } + it { is_expected.not_to include @master } + it { is_expected.to include @invited_member } + it { is_expected.to include @accepted_invite_member } + it { is_expected.not_to include @requested_member } + it { is_expected.to include @accepted_request_member } + it { is_expected.not_to include @blocked_master } + it { is_expected.not_to include @blocked_developer } + end + describe '.owners_and_masters' do it { expect(described_class.owners_and_masters).to include @owner } it { expect(described_class.owners_and_masters).to include @master } @@ -132,6 +154,20 @@ describe Member, models: true do it { expect(described_class.owners_and_masters).not_to include @accepted_invite_member } it { expect(described_class.owners_and_masters).not_to include @requested_member } it { expect(described_class.owners_and_masters).not_to include @accepted_request_member } + it { expect(described_class.owners_and_masters).not_to include @blocked_master } + end + + describe '.has_access' do + subject { described_class.has_access.to_a } + + it { is_expected.to include @owner } + it { is_expected.to include @master } + it { is_expected.to include @invited_member } + it { is_expected.to include @accepted_invite_member } + it { is_expected.not_to include @requested_member } + it { is_expected.to include @accepted_request_member } + it { is_expected.not_to include @blocked_master } + it { is_expected.not_to include @blocked_developer } end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5bf3b8e609e..3b815ded2d3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1038,4 +1038,81 @@ describe MergeRequest, models: true do end end end + + describe '#closed_without_source_project?' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } + let(:destroy_service) { Projects::DestroyService.new(fork_project, user) } + + context 'when the merge request is closed' do + let(:closed_merge_request) do + create(:closed_merge_request, + source_project: fork_project, + target_project: project) + end + + it 'returns false if the source project exists' do + expect(closed_merge_request.closed_without_source_project?).to be_falsey + end + + it 'returns true if the source project does not exist' do + destroy_service.execute + closed_merge_request.reload + + expect(closed_merge_request.closed_without_source_project?).to be_truthy + end + end + + context 'when the merge request is open' do + it 'returns false' do + expect(subject.closed_without_source_project?).to be_falsey + end + end + end + + describe '#reopenable?' do + context 'when the merge request is closed' do + it 'returns true' do + subject.close + + expect(subject.reopenable?).to be_truthy + end + + context 'forked project' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } + let(:merge_request) do + create(:closed_merge_request, + source_project: fork_project, + target_project: project) + end + + it 'returns false if unforked' do + Projects::UnlinkForkService.new(fork_project, user).execute + + expect(merge_request.reload.reopenable?).to be_falsey + end + + it 'returns false if the source project is deleted' do + Projects::DestroyService.new(fork_project, user).execute + + expect(merge_request.reload.reopenable?).to be_falsey + end + + it 'returns false if the merge request is merged' do + merge_request.update_attributes(state: 'merged') + + expect(merge_request.reload.reopenable?).to be_falsey + end + end + end + + context 'when the merge request is opened' do + it 'returns false' do + expect(subject.reopenable?).to be_falsey + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 4a41fafb84d..7ca1bd1e5c9 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -6,6 +6,7 @@ describe Project, models: true do it { is_expected.to belong_to(:namespace) } it { is_expected.to belong_to(:creator).class_name('User') } it { is_expected.to have_many(:users) } + it { is_expected.to have_many(:services) } it { is_expected.to have_many(:events).dependent(:destroy) } it { is_expected.to have_many(:merge_requests).dependent(:destroy) } it { is_expected.to have_many(:issues).dependent(:destroy) } @@ -24,6 +25,30 @@ describe Project, models: true do it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } it { is_expected.to have_one(:board).dependent(:destroy) } + it { is_expected.to have_one(:campfire_service).dependent(:destroy) } + it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) } + it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } + it { is_expected.to have_one(:builds_email_service).dependent(:destroy) } + it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } + it { is_expected.to have_one(:irker_service).dependent(:destroy) } + it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) } + it { is_expected.to have_one(:hipchat_service).dependent(:destroy) } + it { is_expected.to have_one(:flowdock_service).dependent(:destroy) } + it { is_expected.to have_one(:assembla_service).dependent(:destroy) } + it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) } + it { is_expected.to have_one(:buildkite_service).dependent(:destroy) } + it { is_expected.to have_one(:bamboo_service).dependent(:destroy) } + it { is_expected.to have_one(:teamcity_service).dependent(:destroy) } + it { is_expected.to have_one(:jira_service).dependent(:destroy) } + it { is_expected.to have_one(:redmine_service).dependent(:destroy) } + it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) } + it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) } + it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) } + it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) } + it { is_expected.to have_one(:project_feature).dependent(:destroy) } + it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) } + it { is_expected.to have_one(:last_event).class_name('Event') } + it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) } it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:pipelines) } it { is_expected.to have_many(:builds) } @@ -31,9 +56,16 @@ describe Project, models: true do it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } + it { is_expected.to have_many(:labels).dependent(:destroy) } + it { is_expected.to have_many(:users_star_projects).dependent(:destroy) } it { is_expected.to have_many(:environments).dependent(:destroy) } it { is_expected.to have_many(:deployments).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } + it { is_expected.to have_many(:releases).dependent(:destroy) } + it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) } + it { is_expected.to have_many(:project_group_links).dependent(:destroy) } + it { is_expected.to have_many(:notification_settings).dependent(:destroy) } + it { is_expected.to have_many(:forks).through(:forked_project_links) } describe '#members & #requesters' do let(:project) { create(:project) } @@ -178,7 +210,7 @@ describe Project, models: true do expect(project.runners_token).not_to eq('') end - it 'does not set an random toke if one provided' do + it 'does not set an random token if one provided' do project = FactoryGirl.create :empty_project, runners_token: 'my-token' expect(project.runners_token).to eq('my-token') end @@ -1385,6 +1417,68 @@ describe Project, models: true do end end + describe '#lfs_enabled?' do + let(:project) { create(:project) } + + shared_examples 'project overrides group' do + it 'returns true when enabled in project' do + project.update_attribute(:lfs_enabled, true) + + expect(project.lfs_enabled?).to be_truthy + end + + it 'returns false when disabled in project' do + project.update_attribute(:lfs_enabled, false) + + expect(project.lfs_enabled?).to be_falsey + end + + it 'returns the value from the namespace, when no value is set in project' do + expect(project.lfs_enabled?).to eq(project.namespace.lfs_enabled?) + end + end + + context 'LFS disabled in group' do + before do + project.namespace.update_attribute(:lfs_enabled, false) + enable_lfs + end + + it_behaves_like 'project overrides group' + end + + context 'LFS enabled in group' do + before do + project.namespace.update_attribute(:lfs_enabled, true) + enable_lfs + end + + it_behaves_like 'project overrides group' + end + + describe 'LFS disabled globally' do + shared_examples 'it always returns false' do + it do + expect(project.lfs_enabled?).to be_falsey + expect(project.namespace.lfs_enabled?).to be_falsey + end + end + + context 'when no values are set' do + it_behaves_like 'it always returns false' + end + + context 'when all values are set to true' do + before do + project.namespace.update_attribute(:lfs_enabled, true) + project.update_attribute(:lfs_enabled, true) + end + + it_behaves_like 'it always returns false' + end + end + end + describe '.where_paths_in' do context 'without any paths' do it 'returns an empty relation' do @@ -1497,4 +1591,60 @@ describe Project, models: true do project.change_head(project.default_branch) end end + + describe '#pushes_since_gc' do + let(:project) { create(:project) } + + after do + project.reset_pushes_since_gc + end + + context 'without any pushes' do + it 'returns 0' do + expect(project.pushes_since_gc).to eq(0) + end + end + + context 'with a number of pushes' do + it 'returns the number of pushes' do + 3.times { project.increment_pushes_since_gc } + + expect(project.pushes_since_gc).to eq(3) + end + end + end + + describe '#increment_pushes_since_gc' do + let(:project) { create(:project) } + + after do + project.reset_pushes_since_gc + end + + it 'increments the number of pushes since the last GC' do + 3.times { project.increment_pushes_since_gc } + + expect(project.pushes_since_gc).to eq(3) + end + end + + describe '#reset_pushes_since_gc' do + let(:project) { create(:project) } + + after do + project.reset_pushes_since_gc + end + + it 'resets the number of pushes since the last GC' do + 3.times { project.increment_pushes_since_gc } + + project.reset_pushes_since_gc + + expect(project.pushes_since_gc).to eq(0) + end + end + + def enable_lfs + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 7624050878e..94681004c96 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -186,32 +186,6 @@ describe Repository, models: true do it { is_expected.to be_an String } it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") } end - - describe 'parsing result' do - subject { repository.parse_search_result(search_result) } - let(:search_result) { results.first } - - it { is_expected.to be_an OpenStruct } - it { expect(subject.filename).to eq('CHANGELOG') } - it { expect(subject.basename).to eq('CHANGELOG') } - it { expect(subject.ref).to eq('master') } - it { expect(subject.startline).to eq(186) } - it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") } - - context "when filename has extension" do - let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" } - - it { expect(subject.filename).to eq('CONTRIBUTE.md') } - it { expect(subject.basename).to eq('CONTRIBUTE') } - end - - context "when file under directory" do - let(:search_result) { "master:a/b/c.md:5:a b c\n" } - - it { expect(subject.filename).to eq('a/b/c.md') } - it { expect(subject.basename).to eq('a/b/c') } - end - end end describe "#changelog" do diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 2d6093fec7a..7aa7e85a9e2 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -117,17 +117,36 @@ describe API::CommitStatuses, api: true do let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" } context 'developer user' do - context 'only required parameters' do - before { post api(post_url, developer), state: 'success' } + %w[pending running success failed canceled].each do |status| + context "for #{status}" do + context 'uses only required parameters' do + it 'creates commit status' do + post api(post_url, developer), state: status + + expect(response).to have_http_status(201) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq(status) + expect(json_response['name']).to eq('default') + expect(json_response['ref']).not_to be_empty + expect(json_response['target_url']).to be_nil + expect(json_response['description']).to be_nil + end + end + end + end - it 'creates commit status' do - expect(response).to have_http_status(201) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq('success') - expect(json_response['name']).to eq('default') - expect(json_response['ref']).to be_nil - expect(json_response['target_url']).to be_nil - expect(json_response['description']).to be_nil + context 'transitions status from pending' do + before do + post api(post_url, developer), state: 'pending' + end + + %w[running success failed canceled].each do |status| + it "to #{status}" do + expect { post api(post_url, developer), state: status }.not_to change { CommitStatus.count } + + expect(response).to have_http_status(201) + expect(json_response['status']).to eq(status) + end end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 47344a13b5e..f840778ae9b 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -17,21 +17,27 @@ describe API::API, api: true do assignee: user, project: project, state: :closed, - milestone: milestone + milestone: milestone, + created_at: generate(:issue_created_at), + updated_at: 3.hours.ago end let!(:confidential_issue) do create :issue, :confidential, project: project, author: author, - assignee: assignee + assignee: assignee, + created_at: generate(:issue_created_at), + updated_at: 2.hours.ago end let!(:issue) do create :issue, author: user, assignee: user, project: project, - milestone: milestone + milestone: milestone, + created_at: generate(:issue_created_at), + updated_at: 1.hour.ago end let!(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) @@ -135,6 +141,42 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(0) end + + it 'sorts by created_at descending by default' do + get api('/issues', user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api('/issues?sort=asc', user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api('/issues?order_by=updated_at', user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api('/issues?order_by=updated_at&sort=asc', user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end end @@ -147,21 +189,24 @@ describe API::API, api: true do assignee: user, project: group_project, state: :closed, - milestone: group_milestone + milestone: group_milestone, + updated_at: 3.hours.ago end let!(:group_confidential_issue) do create :issue, :confidential, project: group_project, author: author, - assignee: assignee + assignee: assignee, + updated_at: 2.hours.ago end let!(:group_issue) do create :issue, author: user, assignee: user, project: group_project, - milestone: group_milestone + milestone: group_milestone, + updated_at: 1.hour.ago end let!(:group_label) do create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) @@ -278,6 +323,42 @@ describe API::API, api: true do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(group_closed_issue.id) end + + it 'sorts by created_at descending by default' do + get api(base_url, user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api("#{base_url}?sort=asc", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api("#{base_url}?order_by=updated_at", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api("#{base_url}?order_by=updated_at&sort=asc", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end describe "GET /projects/:id/issues" do @@ -386,6 +467,42 @@ describe API::API, api: true do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) end + + it 'sorts by created_at descending by default' do + get api("#{base_url}/issues", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api("#{base_url}/issues?sort=asc", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api("#{base_url}/issues?order_by=updated_at", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api("#{base_url}/issues?order_by=updated_at&sort=asc", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end describe "GET /projects/:id/issues/:issue_id" do diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 9e390bea50b..780bd7f2859 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -15,6 +15,25 @@ describe Ci::API::API do describe "POST /builds/register" do let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' } + + shared_examples 'no builds available' do + context 'when runner sends version in User-Agent' do + context 'for stable version' do + it { expect(response).to have_http_status(204) } + end + + context 'for beta version' do + let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (1-5-stable; go1.6.3; linux/amd64)' } + it { expect(response).to have_http_status(204) } + end + end + + context "when runner doesn't send version in User-Agent" do + let(:user_agent) { 'Go-http-client/1.1' } + it { expect(response).to have_http_status(404) } + end + end it "starts a build" do register_builds info: { platform: :darwin } @@ -33,36 +52,30 @@ describe Ci::API::API do context 'when builds are finished' do before do build.success - end - - it "returns 404 error if no builds for specific runner" do register_builds - - expect(response).to have_http_status(404) end + + it_behaves_like 'no builds available' end context 'for other project with builds' do before do build.success create(:ci_build, :pending) - end - - it "returns 404 error if no builds for shared runner" do register_builds - - expect(response).to have_http_status(404) end + + it_behaves_like 'no builds available' end context 'for shared runner' do let(:shared_runner) { create(:ci_runner, token: "SharedRunner") } - it "should return 404 error if no builds for shared runner" do + before do register_builds shared_runner.token - - expect(response).to have_http_status(404) end + + it_behaves_like 'no builds available' end context 'for triggered build' do @@ -136,18 +149,27 @@ describe Ci::API::API do end context 'when runner is not allowed to pick untagged builds' do - before { runner.update_column(:run_untagged, false) } - - it 'does not pick build' do + before do + runner.update_column(:run_untagged, false) register_builds - - expect(response).to have_http_status 404 end + + it_behaves_like 'no builds available' end end + context 'when runner is paused' do + let(:inactive_runner) { create(:ci_runner, :inactive, token: "InactiveRunner") } + + before do + register_builds inactive_runner.token + end + + it { expect(response).to have_http_status 404 } + end + def register_builds(token = runner.token, **params) - post ci_api("/builds/register"), params.merge(token: token) + post ci_api("/builds/register"), params.merge(token: token), { 'User-Agent' => user_agent } end end diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index ad0d58672b3..c6160f4fa57 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -4,12 +4,11 @@ describe Projects::HousekeepingService do subject { Projects::HousekeepingService.new(project) } let(:project) { create :project } - describe 'execute' do - before do - project.pushes_since_gc = 3 - project.save! - end + after do + project.reset_pushes_since_gc + end + describe '#execute' do it 'enqueues a sidekiq job' do expect(subject).to receive(:try_obtain_lease).and_return(true) expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id) @@ -32,12 +31,12 @@ describe Projects::HousekeepingService do it 'does not reset pushes_since_gc' do expect do expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) - end.not_to change { project.pushes_since_gc }.from(3) + end.not_to change { project.pushes_since_gc } end end end - describe 'needed?' do + describe '#needed?' do it 'when the count is low enough' do expect(subject.needed?).to eq(false) end @@ -48,25 +47,11 @@ describe Projects::HousekeepingService do end end - describe 'increment!' do - let(:lease_key) { "project_housekeeping:increment!:#{project.id}" } - + describe '#increment!' do it 'increments the pushes_since_gc counter' do - lease = double(:lease, try_obtain: true) - expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease) - expect do subject.increment! end.to change { project.pushes_since_gc }.from(0).to(1) end - - it 'does not increment when no lease can be obtained' do - lease = double(:lease, try_obtain: false) - expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease) - - expect do - subject.increment! - end.not_to change { project.pushes_since_gc } - end end end diff --git a/spec/support/wait_for_vue_resource.rb b/spec/support/wait_for_vue_resource.rb new file mode 100644 index 00000000000..1029f84716f --- /dev/null +++ b/spec/support/wait_for_vue_resource.rb @@ -0,0 +1,7 @@ +module WaitForVueResource + def wait_for_vue_resource(spinner: true) + Timeout.timeout(Capybara.default_max_wait_time) do + loop until page.evaluate_script('Vue.activeResources').zero? + end + end +end diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb new file mode 100644 index 00000000000..c5b16c1c304 --- /dev/null +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe 'projects/pipelines/show' do + include Devise::TestHelpers + + let(:project) { create(:project) } + let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) } + + before do + controller.prepend_view_path('app/views/projects') + + create_build('build', 0, 'build') + create_build('test', 1, 'rspec 0:2') + create_build('test', 1, 'rspec 1:2') + create_build('test', 1, 'audit') + create_build('deploy', 2, 'production') + + create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3) + + assign(:project, project) + assign(:pipeline, pipeline) + + allow(view).to receive(:can?).and_return(true) + end + + it 'shows a graph with grouped stages' do + render + + expect(rendered).to have_css('.pipeline-graph') + expect(rendered).to have_css('.grouped-pipeline-dropdown') + + # stages + expect(rendered).to have_text('Build') + expect(rendered).to have_text('Test') + expect(rendered).to have_text('Deploy') + expect(rendered).to have_text('External') + + # builds + expect(rendered).to have_text('rspec') + expect(rendered).to have_text('rspec 0:2') + expect(rendered).to have_text('production') + expect(rendered).to have_text('jenkins') + end + + private + + def create_build(stage, stage_idx, name) + create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name) + end +end diff --git a/vendor/gitignore/Global/NetBeans.gitignore b/vendor/gitignore/Global/NetBeans.gitignore index 520d91ff584..254108cd23b 100644 --- a/vendor/gitignore/Global/NetBeans.gitignore +++ b/vendor/gitignore/Global/NetBeans.gitignore @@ -3,5 +3,4 @@ build/ nbbuild/ dist/ nbdist/ -nbactions.xml .nb-gradle/ diff --git a/vendor/gitignore/Global/Tags.gitignore b/vendor/gitignore/Global/Tags.gitignore index c0318165a27..91927af4cd6 100644 --- a/vendor/gitignore/Global/Tags.gitignore +++ b/vendor/gitignore/Global/Tags.gitignore @@ -9,6 +9,7 @@ gtags.files GTAGS GRTAGS GPATH +GSYMS cscope.files cscope.out cscope.in.out diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/macOS.gitignore index 5972fe50f66..828a509a137 100644 --- a/vendor/gitignore/Global/OSX.gitignore +++ b/vendor/gitignore/Global/macOS.gitignore @@ -3,7 +3,8 @@ .LSOverride # Icon must end with two \r -Icon
+Icon + # Thumbnails ._* diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore index a4ee41ab62b..450f32ec40c 100644 --- a/vendor/gitignore/Haskell.gitignore +++ b/vendor/gitignore/Haskell.gitignore @@ -17,3 +17,4 @@ cabal.sandbox.config *.eventlog .stack-work/ cabal.project.local +.HTF/ diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore index 0d7a0de298f..93103fdbe77 100644 --- a/vendor/gitignore/Joomla.gitignore +++ b/vendor/gitignore/Joomla.gitignore @@ -52,6 +52,7 @@ /administrator/language/en-GB/en-GB.plg_content_contact.sys.ini /administrator/language/en-GB/en-GB.plg_content_finder.ini /administrator/language/en-GB/en-GB.plg_content_finder.sys.ini +/administrator/language/en-GB/en-GB.plg_editors-xtd_module* /administrator/language/en-GB/en-GB.plg_finder_categories.ini /administrator/language/en-GB/en-GB.plg_finder_categories.sys.ini /administrator/language/en-GB/en-GB.plg_finder_contacts.ini @@ -64,6 +65,10 @@ /administrator/language/en-GB/en-GB.plg_finder_tags.sys.ini /administrator/language/en-GB/en-GB.plg_finder_weblinks.ini /administrator/language/en-GB/en-GB.plg_finder_weblinks.sys.ini +/administrator/language/en-GB/en-GB.plg_installer_folderinstaller* +/administrator/language/en-GB/en-GB.plg_installer_packageinstaller* +/administrator/language/en-GB/en-GB.plg_installer_packageinstaller +/administrator/language/en-GB/en-GB.plg_installer_urlinstaller* /administrator/language/en-GB/en-GB.plg_installer_webinstaller.ini /administrator/language/en-GB/en-GB.plg_installer_webinstaller.sys.ini /administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.ini @@ -72,6 +77,8 @@ /administrator/language/en-GB/en-GB.plg_search_tags.sys.ini /administrator/language/en-GB/en-GB.plg_system_languagecode.ini /administrator/language/en-GB/en-GB.plg_system_languagecode.sys.ini +/administrator/language/en-GB/en-GB.plg_system_stats* +/administrator/language/en-GB/en-GB.plg_system_updatenotification* /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.ini /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.sys.ini /administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.ini @@ -249,8 +256,10 @@ /administrator/language/en-GB/en-GB.tpl_hathor.sys.ini /administrator/language/en-GB/en-GB.xml /administrator/language/en-GB/index.html +/administrator/language/ru-RU/index.html /administrator/language/overrides/* /administrator/language/index.html +/administrator/logs/index.html /administrator/manifests/* /administrator/modules/mod_custom/* /administrator/modules/mod_feed/* @@ -289,6 +298,7 @@ /components/com_finder/* /components/com_mailto/* /components/com_media/* +/components/com_modules/* /components/com_newsfeeds/* /components/com_search/* /components/com_users/* @@ -407,6 +417,7 @@ /libraries/idna_convert/* /libraries/joomla/* /libraries/legacy/* +/libraries/php-encryption/* /libraries/phpass/* /libraries/phpmailer/* /libraries/phputf8/* @@ -431,9 +442,11 @@ /media/media/* /media/mod_languages/* /media/overrider/* +/media/plg_captcha_recaptcha/* /media/plg_quickicon_extensionupdate/* /media/plg_quickicon_joomlaupdate/* /media/plg_system_highlight/* +/media/plg_system_stats/* /media/system/* /media/index.html /modules/mod_articles_archive/* @@ -486,6 +499,7 @@ /plugins/editors/none/* /plugins/editors/tinymce/* /plugins/editors/index.html +/plugins/editors-xtd/module/* /plugins/editors-xtd/article/* /plugins/editors-xtd/image/* /plugins/editors-xtd/pagebreak/* @@ -523,6 +537,8 @@ /plugins/system/redirect/* /plugins/system/remember/* /plugins/system/sef/* +/plugins/system/stats/* +/plugins/system/updatenotification/* /plugins/system/index.html /plugins/twofactorauth/* /plugins/user/contactcreator/* diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index aea5294de9d..bf7525f9912 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -34,5 +34,8 @@ jspm_packages # Optional npm cache directory .npm +# Optional eslint cache +.eslintcache + # Optional REPL history .node_repl_history diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore index 20592083931..58c51ecaed4 100644 --- a/vendor/gitignore/Objective-C.gitignore +++ b/vendor/gitignore/Objective-C.gitignore @@ -50,7 +50,9 @@ Carthage/Build # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md fastlane/report.xml +fastlane/Preview.html fastlane/screenshots +fastlane/test_output # Code Injection # diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore index 72364f99fe4..37fc9d40817 100644 --- a/vendor/gitignore/Python.gitignore +++ b/vendor/gitignore/Python.gitignore @@ -79,6 +79,7 @@ celerybeat-schedule .env # virtualenv +.venv/ venv/ ENV/ diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore index d8c256c1925..e97427608c1 100644 --- a/vendor/gitignore/Rails.gitignore +++ b/vendor/gitignore/Rails.gitignore @@ -12,9 +12,11 @@ capybara-*.html rerun.txt pickle-email-*.html -# TODO Comment out these rules if you are OK with secrets being uploaded to the repo +# TODO Comment out this rule if you are OK with secrets being uploaded to the repo config/initializers/secret_token.rb -config/secrets.yml + +# Only include if you have production secrets in this file, which is no longer a Rails default +# config/secrets.yml # dotenv # TODO Comment out this rule if environment variables can be committed diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index 67acbf42f5e..d56f8b53288 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -251,3 +251,10 @@ paket-files/ # JetBrains Rider .idea/ *.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml index 396d3f1b042..f3fa3949656 100644 --- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml @@ -1,7 +1,12 @@ # Official docker image. image: docker:latest +services: + - docker:dind + build: stage: build script: - - docker build -t test . + - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" $CI_REGISTRY + - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME" . + - docker push "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME" diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml index 166f146ee05..08b57c8c0ac 100644 --- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -43,3 +43,12 @@ rails: - bundle exec rake db:migrate - bundle exec rake db:seed - bundle exec rake test + +# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk +# are supported too: https://github.com/travis-ci/dpl +deploy: + type: deploy + environment: production + script: + - gem install dpl + - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY diff --git a/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml new file mode 100644 index 00000000000..c9c35906d1c --- /dev/null +++ b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml @@ -0,0 +1,30 @@ +# Lifted from: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/ +# This file assumes an own GitLab CI runner, setup on an OS X system. +stages: + - build + - archive + +build_project: + stage: build + script: + - xcodebuild clean -project ProjectName.xcodeproj -scheme SchemeName | xcpretty + - xcodebuild test -project ProjectName.xcodeproj -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.2' | xcpretty -s + tags: + - ios_9-2 + - xcode_7-2 + - osx_10-11 + +archive_project: + stage: archive + script: + - xcodebuild clean archive -archivePath build/ProjectName -scheme SchemeName + - xcodebuild -exportArchive -exportFormat ipa -archivePath "build/ProjectName.xcarchive" -exportPath "build/ProjectName.ipa" -exportProvisioningProfile "ProvisioningProfileName" + only: + - master + artifacts: + paths: + - build/ProjectName.ipa + tags: + - ios_9-2 + - xcode_7-2 + - osx_10-11 |