diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..7e800609e6
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+CHANGELOG merge=union
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 92ca729dc1..7a7b5c9393 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,40 +1,42 @@
-.bundle
-.rbx/
-db/*.sqlite3
-db/*.sqlite3-journal
-log/*.log*
-tmp/
-.sass-cache/
-coverage/*
-backups/*
+*.log
*.swp
-public/uploads/
-.ruby-version
-.ruby-gemset
-.rvmrc
-.rbenv-version
+.DS_Store
+.bundle
+.chef
.directory
-nohup.out
-Vagrantfile
+.envrc
+.gitlab_shell_secret
+.idea
+.rbenv-version
+.rbx/
+.ruby-gemset
+.ruby-version
+.rvmrc
+.sass-cache/
+.secret
.vagrant
-config/gitlab.yml
+Vagrantfile
+backups/*
+config/aws.yml
config/database.yml
+config/gitlab.yml
config/initializers/omniauth.rb
config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb
-config/unicorn.rb
config/resque.yml
-config/aws.yml
+config/unicorn.rb
+coverage/*
+db/*.sqlite3
+db/*.sqlite3-journal
db/data.yml
-.idea
-.DS_Store
-.chef
-vendor/bundle/*
-rails_best_practices_output.html
doc/code/*
-.secret
-*.log
-public/uploads.*
-public/assets/
-.envrc
dump.rdb
+log/*.log*
+nohup.out
+public/assets/
+public/uploads.*
+public/uploads/
+rails_best_practices_output.html
+tags
+tmp/
+vendor/bundle/*
diff --git a/.pkgr.yml b/.pkgr.yml
index 97d78b6ef6..8fc9fddf8f 100644
--- a/.pkgr.yml
+++ b/.pkgr.yml
@@ -1,10 +1,15 @@
user: git
group: git
+services:
+ - postgres
before_precompile: ./bin/pkgr_before_precompile.sh
targets:
debian-7: &wheezy
build_dependencies:
+ - libkrb5-dev
- libicu-dev
+ - cmake
+ - pkg-config
dependencies:
- libicu48
- libpcre3
@@ -12,14 +17,20 @@ targets:
ubuntu-12.04: *wheezy
ubuntu-14.04:
build_dependencies:
+ - libkrb5-dev
- libicu-dev
+ - cmake
+ - pkg-config
dependencies:
- libicu52
- libpcre3
- git
centos-6:
build_dependencies:
+ - krb5-devel
- libicu-devel
+ - cmake
+ - pkgconfig
dependencies:
- libicu
- pcre
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000000..03b78d6884
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,1006 @@
+Style/AccessModifierIndentation:
+ Description: Check indentation of private/protected visibility modifiers.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected'
+ Enabled: true
+
+Style/AccessorMethodName:
+ Description: Check the naming of accessor methods for get_/set_.
+ Enabled: false
+
+Style/Alias:
+ Description: 'Use alias_method instead of alias.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method'
+ Enabled: true
+
+Style/AlignArray:
+ Description: >-
+ Align the elements of an array literal if they span more than
+ one line.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays'
+ Enabled: true
+
+Style/AlignHash:
+ Description: >-
+ Align the elements of a hash literal if they span more than
+ one line.
+ Enabled: true
+
+Style/AlignParameters:
+ Description: >-
+ Align the parameters of a method call if they span more
+ than one line.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent'
+ Enabled: false
+
+Style/AndOr:
+ Description: 'Use &&/|| instead of and/or.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or'
+ Enabled: false
+
+Style/ArrayJoin:
+ Description: 'Use Array#join instead of Array#*.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join'
+ Enabled: false
+
+Style/AsciiComments:
+ Description: 'Use only ascii symbols in comments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments'
+ Enabled: true
+
+Style/AsciiIdentifiers:
+ Description: 'Use only ascii symbols in identifiers.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers'
+ Enabled: true
+
+Style/Attr:
+ Description: 'Checks for uses of Module#attr.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr'
+ Enabled: false
+
+Style/BeginBlock:
+ Description: 'Avoid the use of BEGIN blocks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks'
+ Enabled: true
+
+Style/BarePercentLiterals:
+ Description: 'Checks if usage of %() or %Q() matches configuration.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand'
+ Enabled: false
+
+Style/BlockComments:
+ Description: 'Do not use block comments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments'
+ Enabled: false
+
+Style/BlockEndNewline:
+ Description: 'Put end statement of multiline block on its own line.'
+ Enabled: true
+
+Style/Blocks:
+ Description: >-
+ Avoid using {...} for multi-line blocks (multiline chaining is
+ always ugly).
+ Prefer {...} over do...end for single-line blocks.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
+ Enabled: true
+
+Style/BracesAroundHashParameters:
+ Description: 'Enforce braces style around hash parameters.'
+ Enabled: false
+
+Style/CaseEquality:
+ Description: 'Avoid explicit use of the case equality operator(===).'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality'
+ Enabled: false
+
+Style/CaseIndentation:
+ Description: 'Indentation of when in a case/when/[else/]end.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case'
+ Enabled: true
+
+Style/CharacterLiteral:
+ Description: 'Checks for uses of character literals.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals'
+ Enabled: true
+
+Style/ClassAndModuleCamelCase:
+ Description: 'Use CamelCase for classes and modules.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes'
+ Enabled: true
+
+Style/ClassAndModuleChildren:
+ Description: 'Checks style of children classes and modules.'
+ Enabled: false
+
+Style/ClassCheck:
+ Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.'
+ Enabled: false
+
+Style/ClassMethods:
+ Description: 'Use self when defining module/class methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-singletons'
+ Enabled: false
+
+Style/ClassVars:
+ Description: 'Avoid the use of class variables.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars'
+ Enabled: true
+
+Style/ColonMethodCall:
+ Description: 'Do not use :: for method call.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons'
+ Enabled: false
+
+Style/CommentAnnotation:
+ Description: >-
+ Checks formatting of special comments
+ (TODO, FIXME, OPTIMIZE, HACK, REVIEW).
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords'
+ Enabled: false
+
+Style/CommentIndentation:
+ Description: 'Indentation of comments.'
+ Enabled: true
+
+Style/ConstantName:
+ Description: 'Constants should use SCREAMING_SNAKE_CASE.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case'
+ Enabled: true
+
+Style/DefWithParentheses:
+ Description: 'Use def with parentheses when there are arguments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
+ Enabled: false
+
+Style/DeprecatedHashMethods:
+ Description: 'Checks for use of deprecated Hash methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key'
+ Enabled: false
+
+Style/Documentation:
+ Description: 'Document classes and non-namespace modules.'
+ Enabled: false
+
+Style/DotPosition:
+ Description: 'Checks the position of the dot in multi-line method calls.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains'
+ Enabled: false
+
+Style/DoubleNegation:
+ Description: 'Checks for uses of double negation (!!).'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang'
+ Enabled: false
+
+Style/EachWithObject:
+ Description: 'Prefer `each_with_object` over `inject` or `reduce`.'
+ Enabled: false
+
+Style/ElseAlignment:
+ Description: 'Align elses and elsifs correctly.'
+ Enabled: true
+
+Style/EmptyElse:
+ Description: 'Avoid empty else-clauses.'
+ Enabled: false
+
+Style/EmptyLineBetweenDefs:
+ Description: 'Use empty lines between defs.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods'
+ Enabled: false
+
+Style/EmptyLines:
+ Description: "Don't use several empty lines in a row."
+ Enabled: false
+
+Style/EmptyLinesAroundAccessModifier:
+ Description: "Keep blank lines around access modifiers."
+ Enabled: false
+
+Style/EmptyLinesAroundBlockBody:
+ Description: "Keeps track of empty lines around block bodies."
+ Enabled: false
+
+Style/EmptyLinesAroundClassBody:
+ Description: "Keeps track of empty lines around class bodies."
+ Enabled: false
+
+Style/EmptyLinesAroundModuleBody:
+ Description: "Keeps track of empty lines around module bodies."
+ Enabled: false
+
+Style/EmptyLinesAroundMethodBody:
+ Description: "Keeps track of empty lines around method bodies."
+ Enabled: false
+
+Style/EmptyLiteral:
+ Description: 'Prefer literals to Array.new/Hash.new/String.new.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash'
+ Enabled: false
+
+Style/EndBlock:
+ Description: 'Avoid the use of END blocks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks'
+ Enabled: false
+
+Style/EndOfLine:
+ Description: 'Use Unix-style line endings.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf'
+ Enabled: false
+
+Style/EvenOdd:
+ Description: 'Favor the use of Fixnum#even? && Fixnum#odd?'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
+ Enabled: false
+
+Style/FileName:
+ Description: 'Use snake_case for source file names.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
+ Enabled: false
+
+Style/FlipFlop:
+ Description: 'Checks for flip flops'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops'
+ Enabled: false
+
+Style/For:
+ Description: 'Checks use of for or each in multiline loops.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops'
+ Enabled: false
+
+Style/FormatString:
+ Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf'
+ Enabled: false
+
+Style/GlobalVars:
+ Description: 'Do not introduce global variables.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars'
+ Enabled: false
+
+Style/GuardClause:
+ Description: 'Check for conditionals that can be replaced with guard clauses'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
+ Enabled: false
+
+Style/HashSyntax:
+ Description: >-
+ Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax
+ { :a => 1, :b => 2 }.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals'
+ Enabled: true
+
+Style/IfUnlessModifier:
+ Description: >-
+ Favor modifier if/unless usage when you have a
+ single-line body.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier'
+ Enabled: false
+
+Style/IfWithSemicolon:
+ Description: 'Do not use if x; .... Use the ternary operator instead.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs'
+ Enabled: false
+
+Style/IndentationConsistency:
+ Description: 'Keep indentation straight.'
+ Enabled: true
+
+Style/IndentationWidth:
+ Description: 'Use 2 spaces for indentation.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
+ Enabled: true
+
+Style/IndentArray:
+ Description: >-
+ Checks the indentation of the first element in an array
+ literal.
+ Enabled: false
+
+Style/IndentHash:
+ Description: 'Checks the indentation of the first key in a hash literal.'
+ Enabled: false
+
+Style/InfiniteLoop:
+ Description: 'Use Kernel#loop for infinite loops.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop'
+ Enabled: false
+
+Style/Lambda:
+ Description: 'Use the new lambda literal syntax for single-line blocks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line'
+ Enabled: false
+
+Style/LambdaCall:
+ Description: 'Use lambda.call(...) instead of lambda.(...).'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call'
+ Enabled: false
+
+Style/LeadingCommentSpace:
+ Description: 'Comments should start with a space.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space'
+ Enabled: false
+
+Style/LineEndConcatenation:
+ Description: >-
+ Use \ instead of + or << to concatenate two string literals at
+ line end.
+ Enabled: false
+
+Style/MethodCallParentheses:
+ Description: 'Do not use parentheses for method calls with no arguments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens'
+ Enabled: false
+
+Style/MethodDefParentheses:
+ Description: >-
+ Checks if the method definitions have or don't have
+ parentheses.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
+ Enabled: false
+
+Style/MethodName:
+ Description: 'Use the configured style when naming methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars'
+ Enabled: false
+
+Style/ModuleFunction:
+ Description: 'Checks for usage of `extend self` in modules.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function'
+ Enabled: false
+
+Style/MultilineBlockChain:
+ Description: 'Avoid multi-line chains of blocks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
+ Enabled: false
+
+Style/MultilineBlockLayout:
+ Description: 'Ensures newlines after multiline block do statements.'
+ Enabled: true
+
+Style/MultilineIfThen:
+ Description: 'Do not use then for multi-line if/unless.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then'
+ Enabled: false
+
+Style/MultilineOperationIndentation:
+ Description: >-
+ Checks indentation of binary operations that span more than
+ one line.
+ Enabled: false
+
+Style/MultilineTernaryOperator:
+ Description: >-
+ Avoid multi-line ?: (the ternary operator);
+ use if/unless instead.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary'
+ Enabled: false
+
+Style/NegatedIf:
+ Description: >-
+ Favor unless over if for negative conditions
+ (or control flow or).
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives'
+ Enabled: false
+
+Style/NegatedWhile:
+ Description: 'Favor until over while for negative conditions.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives'
+ Enabled: false
+
+Style/NestedTernaryOperator:
+ Description: 'Use one expression per branch in a ternary operator.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary'
+ Enabled: true
+
+Style/Next:
+ Description: 'Use `next` to skip iteration instead of a condition at the end.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
+ Enabled: false
+
+Style/NilComparison:
+ Description: 'Prefer x.nil? to x == nil.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
+ Enabled: true
+
+Style/NonNilCheck:
+ Description: 'Checks for redundant nil checks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks'
+ Enabled: true
+
+Style/Not:
+ Description: 'Use ! instead of not.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not'
+ Enabled: true
+
+Style/NumericLiterals:
+ Description: >-
+ Add underscores to large numeric literals to improve their
+ readability.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics'
+ Enabled: false
+
+Style/OneLineConditional:
+ Description: >-
+ Favor the ternary operator(?:) over
+ if/then/else/end constructs.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator'
+ Enabled: true
+
+Style/OpMethod:
+ Description: 'When defining binary operators, name the argument other.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'
+ Enabled: false
+
+Style/ParenthesesAroundCondition:
+ Description: >-
+ Don't use parentheses around the condition of an
+ if/unless/while.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if'
+ Enabled: true
+
+Style/PercentLiteralDelimiters:
+ Description: 'Use `%`-literal delimiters consistently'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces'
+ Enabled: false
+
+Style/PercentQLiterals:
+ Description: 'Checks if uses of %Q/%q match the configured preference.'
+ Enabled: false
+
+Style/PerlBackrefs:
+ Description: 'Avoid Perl-style regex back references.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers'
+ Enabled: false
+
+Style/PredicateName:
+ Description: 'Check the names of predicate methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark'
+ Enabled: false
+
+Style/Proc:
+ Description: 'Use proc instead of Proc.new.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc'
+ Enabled: false
+
+Style/RaiseArgs:
+ Description: 'Checks the arguments passed to raise/fail.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages'
+ Enabled: false
+
+Style/RedundantBegin:
+ Description: "Don't use begin blocks when they are not needed."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit'
+ Enabled: false
+
+Style/RedundantException:
+ Description: "Checks for an obsolete RuntimeException argument in raise/fail."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror'
+ Enabled: false
+
+Style/RedundantReturn:
+ Description: "Don't use return where it's not required."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return'
+ Enabled: true
+
+Style/RedundantSelf:
+ Description: "Don't use self where it's not needed."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required'
+ Enabled: false
+
+Style/RegexpLiteral:
+ Description: >-
+ Use %r for regular expressions matching more than
+ `MaxSlashes` '/' characters.
+ Use %r only for regular expressions matching more than
+ `MaxSlashes` '/' character.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r'
+ Enabled: false
+
+Style/RescueModifier:
+ Description: 'Avoid using rescue in its modifier form.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers'
+ Enabled: false
+
+Style/SelfAssignment:
+ Description: >-
+ Checks for places where self-assignment shorthand should have
+ been used.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment'
+ Enabled: false
+
+Style/Semicolon:
+ Description: "Don't use semicolons to terminate expressions."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon'
+ Enabled: false
+
+Style/SignalException:
+ Description: 'Checks for proper usage of fail and raise.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method'
+ Enabled: false
+
+Style/SingleLineBlockParams:
+ Description: 'Enforces the names of some block params.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks'
+ Enabled: false
+
+Style/SingleLineMethods:
+ Description: 'Avoid single-line methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods'
+ Enabled: false
+
+Style/SingleSpaceBeforeFirstArg:
+ Description: >-
+ Checks that exactly one space is used between a method name
+ and the first argument for method calls without parentheses.
+ Enabled: false
+
+Style/SpaceAfterColon:
+ Description: 'Use spaces after colons.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceAfterComma:
+ Description: 'Use spaces after commas.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceAfterControlKeyword:
+ Description: 'Use spaces after if/elsif/unless/while/until/case/when.'
+ Enabled: false
+
+Style/SpaceAfterMethodName:
+ Description: >-
+ Do not put a space between a method name and the opening
+ parenthesis in a method definition.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
+ Enabled: false
+
+Style/SpaceAfterNot:
+ Description: Tracks redundant space after the ! operator.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang'
+ Enabled: false
+
+Style/SpaceAfterSemicolon:
+ Description: 'Use spaces after semicolons.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceBeforeBlockBraces:
+ Description: >-
+ Checks that the left block brace has or doesn't have space
+ before it.
+ Enabled: false
+
+Style/SpaceBeforeComma:
+ Description: 'No spaces before commas.'
+ Enabled: false
+
+Style/SpaceBeforeComment:
+ Description: >-
+ Checks for missing space between code and a comment on the
+ same line.
+ Enabled: false
+
+Style/SpaceBeforeSemicolon:
+ Description: 'No spaces before semicolons.'
+ Enabled: false
+
+Style/SpaceInsideBlockBraces:
+ Description: >-
+ Checks that block braces have or don't have surrounding space.
+ For blocks taking parameters, checks that the left brace has
+ or doesn't have trailing space.
+ Enabled: false
+
+Style/SpaceAroundEqualsInParameterDefault:
+ Description: >-
+ Checks that the equals signs in parameter default assignments
+ have or don't have surrounding space depending on
+ configuration.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals'
+ Enabled: false
+
+Style/SpaceAroundOperators:
+ Description: 'Use spaces around operators.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceBeforeModifierKeyword:
+ Description: 'Put a space before the modifier keyword.'
+ Enabled: false
+
+Style/SpaceInsideBrackets:
+ Description: 'No spaces after [ or before ].'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
+ Enabled: false
+
+Style/SpaceInsideHashLiteralBraces:
+ Description: "Use spaces inside hash literal braces - or don't."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: true
+
+Style/SpaceInsideParens:
+ Description: 'No spaces after ( or before ).'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
+ Enabled: false
+
+Style/SpaceInsideRangeLiteral:
+ Description: 'No spaces inside range literals.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals'
+ Enabled: false
+
+Style/SpecialGlobalVars:
+ Description: 'Avoid Perl-style global variables.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms'
+ Enabled: false
+
+Style/StringLiterals:
+ Description: 'Checks if uses of quotes match the configured preference.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
+ Enabled: false
+
+Style/StringLiteralsInInterpolation:
+ Description: >-
+ Checks if uses of quotes inside expressions in interpolated
+ strings match the configured preference.
+ Enabled: false
+
+Style/SymbolProc:
+ Description: 'Use symbols as procs instead of blocks when possible.'
+ Enabled: false
+
+Style/Tab:
+ Description: 'No hard tabs.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
+ Enabled: true
+
+Style/TrailingBlankLines:
+ Description: 'Checks trailing blank lines and final newline.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof'
+ Enabled: true
+
+Style/TrailingComma:
+ Description: 'Checks for trailing comma in parameter lists and literals.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
+ Enabled: false
+
+Style/TrailingWhitespace:
+ Description: 'Avoid trailing whitespace.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace'
+ Enabled: false
+
+Style/TrivialAccessors:
+ Description: 'Prefer attr_* methods to trivial readers/writers.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family'
+ Enabled: false
+
+Style/UnlessElse:
+ Description: >-
+ Do not use unless with else. Rewrite these with the positive
+ case first.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless'
+ Enabled: false
+
+Style/UnneededCapitalW:
+ Description: 'Checks for %W when interpolation is not needed.'
+ Enabled: false
+
+Style/UnneededPercentQ:
+ Description: 'Checks for %q/%Q when single quotes or double quotes would do.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q'
+ Enabled: false
+
+Style/UnneededPercentX:
+ Description: 'Checks for %x when `` would do.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x'
+ Enabled: false
+
+Style/VariableInterpolation:
+ Description: >-
+ Don't interpolate global, instance and class variables
+ directly in strings.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate'
+ Enabled: false
+
+Style/VariableName:
+ Description: 'Use the configured style when naming variables.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars'
+ Enabled: false
+
+Style/WhenThen:
+ Description: 'Use when x then ... for one-line cases.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases'
+ Enabled: false
+
+Style/WhileUntilDo:
+ Description: 'Checks for redundant do after while or until.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do'
+ Enabled: false
+
+Style/WhileUntilModifier:
+ Description: >-
+ Favor modifier while/until usage when you have a
+ single-line body.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier'
+ Enabled: false
+
+Style/WordArray:
+ Description: 'Use %w or %W for arrays of words.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w'
+ Enabled: false
+
+#################### Metrics ################################
+
+Metrics/AbcSize:
+ Description: >-
+ A calculated magnitude based on number of assignments,
+ branches, and conditions.
+ Enabled: false
+
+Metrics/BlockNesting:
+ Description: 'Avoid excessive block nesting'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
+ Enabled: false
+
+Metrics/ClassLength:
+ Description: 'Avoid classes longer than 100 lines of code.'
+ Enabled: false
+
+Metrics/CyclomaticComplexity:
+ Description: >-
+ A complexity metric that is strongly correlated to the number
+ of test cases needed to validate a method.
+ Enabled: false
+
+Metrics/LineLength:
+ Description: 'Limit lines to 80 characters.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
+ Enabled: false
+
+Metrics/MethodLength:
+ Description: 'Avoid methods longer than 10 lines of code.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
+ Enabled: false
+
+Metrics/ParameterLists:
+ Description: 'Avoid parameter lists longer than three or four parameters.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
+ Enabled: false
+
+Metrics/PerceivedComplexity:
+ Description: >-
+ A complexity metric geared towards measuring complexity for a
+ human reader.
+ Enabled: false
+
+#################### Lint ################################
+### Warnings
+
+Lint/AmbiguousOperator:
+ Description: >-
+ Checks for ambiguous operators in the first argument of a
+ method invocation without parentheses.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args'
+ Enabled: false
+
+Lint/AmbiguousRegexpLiteral:
+ Description: >-
+ Checks for ambiguous regexp literals in the first argument of
+ a method invocation without parenthesis.
+ Enabled: false
+
+Lint/AssignmentInCondition:
+ Description: "Don't use assignment in conditions."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition'
+ Enabled: false
+
+Lint/BlockAlignment:
+ Description: 'Align block ends correctly.'
+ Enabled: false
+
+Lint/ConditionPosition:
+ Description: >-
+ Checks for condition placed in a confusing position relative to
+ the keyword.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition'
+ Enabled: false
+
+Lint/Debugger:
+ Description: 'Check for debugger calls.'
+ Enabled: false
+
+Lint/DefEndAlignment:
+ Description: 'Align ends corresponding to defs correctly.'
+ Enabled: false
+
+Lint/DeprecatedClassMethods:
+ Description: 'Check for deprecated class method calls.'
+ Enabled: false
+
+Lint/ElseLayout:
+ Description: 'Check for odd code arrangement in an else block.'
+ Enabled: false
+
+Lint/EmptyEnsure:
+ Description: 'Checks for empty ensure block.'
+ Enabled: false
+
+Lint/EmptyInterpolation:
+ Description: 'Checks for empty string interpolation.'
+ Enabled: false
+
+Lint/EndAlignment:
+ Description: 'Align ends correctly.'
+ Enabled: false
+
+Lint/EndInMethod:
+ Description: 'END blocks should not be placed inside method definitions.'
+ Enabled: false
+
+Lint/EnsureReturn:
+ Description: 'Do not use return in an ensure block.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure'
+ Enabled: false
+
+Lint/Eval:
+ Description: 'The use of eval represents a serious security risk.'
+ Enabled: false
+
+Lint/HandleExceptions:
+ Description: "Don't suppress exception."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions'
+ Enabled: false
+
+Lint/InvalidCharacterLiteral:
+ Description: >-
+ Checks for invalid character literals with a non-escaped
+ whitespace character.
+ Enabled: false
+
+Lint/LiteralInCondition:
+ Description: 'Checks of literals used in conditions.'
+ Enabled: false
+
+Lint/LiteralInInterpolation:
+ Description: 'Checks for literals used in interpolation.'
+ Enabled: false
+
+Lint/Loop:
+ Description: >-
+ Use Kernel#loop with break rather than begin/end/until or
+ begin/end/while for post-loop tests.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break'
+ Enabled: false
+
+Lint/ParenthesesAsGroupedExpression:
+ Description: >-
+ Checks for method calls with a space before the opening
+ parenthesis.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
+ Enabled: true
+
+Lint/RequireParentheses:
+ Description: >-
+ Use parentheses in the method call to avoid confusion
+ about precedence.
+ Enabled: false
+
+Lint/RescueException:
+ Description: 'Avoid rescuing the Exception class.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues'
+ Enabled: false
+
+Lint/ShadowingOuterLocalVariable:
+ Description: >-
+ Do not use the same name as outer local variable
+ for block arguments or block local variables.
+ Enabled: false
+
+Lint/SpaceBeforeFirstArg:
+ Description: >-
+ Put a space between a method name and the first argument
+ in a method call without parentheses.
+ Enabled: false
+
+Lint/StringConversionInInterpolation:
+ Description: 'Checks for Object#to_s usage in string interpolation.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s'
+ Enabled: false
+
+Lint/UnderscorePrefixedVariableName:
+ Description: 'Do not use prefix `_` for a variable that is used.'
+ Enabled: true
+
+Lint/UnusedBlockArgument:
+ Description: 'Checks for unused block arguments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
+ Enabled: false
+
+Lint/UnusedMethodArgument:
+ Description: 'Checks for unused method arguments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
+ Enabled: false
+
+Lint/UnreachableCode:
+ Description: 'Unreachable code.'
+ Enabled: false
+
+Lint/UselessAccessModifier:
+ Description: 'Checks for useless access modifiers.'
+ Enabled: false
+
+Lint/UselessAssignment:
+ Description: 'Checks for useless assignment to a local variable.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
+ Enabled: false
+
+Lint/UselessComparison:
+ Description: 'Checks for comparison of something with itself.'
+ Enabled: false
+
+Lint/UselessElseWithoutRescue:
+ Description: 'Checks for useless `else` in `begin..end` without `rescue`.'
+ Enabled: false
+
+Lint/UselessSetterCall:
+ Description: 'Checks for useless setter call to a local variable.'
+ Enabled: false
+
+Lint/Void:
+ Description: 'Possible use of operator/literal/variable in void context.'
+ Enabled: false
+
+##################### Rails ##################################
+
+Rails/ActionFilter:
+ Description: 'Enforces consistent use of action filter methods.'
+ Enabled: false
+
+Rails/DefaultScope:
+ Description: 'Checks if the argument passed to default_scope is a block.'
+ Enabled: false
+
+Rails/Delegate:
+ Description: 'Prefer delegate method for delegations.'
+ Enabled: false
+
+Rails/HasAndBelongsToMany:
+ Description: 'Prefer has_many :through to has_and_belongs_to_many.'
+ Enabled: true
+
+Rails/Output:
+ Description: 'Checks for calls to puts, print, etc.'
+ Enabled: true
+
+Rails/ReadWriteAttribute:
+ Description: >-
+ Checks for read_attribute(:attr) and
+ write_attribute(:attr, val).
+ Enabled: false
+
+Rails/ScopeArgs:
+ Description: 'Checks the arguments of ActiveRecord scopes.'
+ Enabled: false
+
+Rails/Validation:
+ Description: 'Use validates :attribute, hash of validations.'
+ Enabled: false
+
+
+# Exclude some of GitLab files
+#
+#
+AllCops:
+ RunRailsCops: true
+ Exclude:
+ - 'spec/**/*'
+ - 'features/**/*'
+ - 'vendor/**/*'
+ - 'db/**/*'
+ - 'tmp/**/*'
+ - 'bin/**/*'
+ - 'lib/backup/**/*'
+ - 'lib/tasks/**/*'
+ - 'lib/email_validator.rb'
+ - 'lib/gitlab/upgrader.rb'
+ - 'lib/gitlab/seeder.rb'
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000000..399088bf46
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+2.1.6
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 9b7b2cb3c0..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-language: ruby
-env:
- global:
- - TRAVIS=true
- matrix:
- - TASK=spinach_project DB=mysql
- - TASK=spinach_other DB=mysql
- - TASK=spec:api DB=mysql
- - TASK=spec:feature DB=mysql
- - TASK=spec:other DB=mysql
- - TASK=jasmine:ci DB=mysql
- - TASK=spinach_project DB=postgresql
- - TASK=spinach_other DB=postgresql
- - TASK=spec:api DB=postgresql
- - TASK=spec:feature DB=postgresql
- - TASK=spec:other DB=postgresql
- - TASK=jasmine:ci DB=postgresql
-before_install:
- - sudo apt-get install libicu-dev -y
-install:
- - "travis_retry bundle install --deployment --without production --retry 5"
-branches:
- only:
- - 'master'
-rvm:
- - 2.0.0
-services:
- - redis-server
-before_script:
- - "cp config/database.yml.$DB config/database.yml"
- - "cp config/gitlab.yml.example config/gitlab.yml"
- - "bundle exec rake db:setup"
- - "bundle exec rake db:seed_fu"
-script: "bundle exec rake $TASK --trace"
-notifications:
- email: false
diff --git a/CHANGELOG b/CHANGELOG
index bc2d34d49f..1aab904f11 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,513 @@
+Please view this file on the master branch, on stable branches it's out of date.
+
+v 7.11.0 (unreleased)
+ - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky)
+ - Ignore invalid lines in .gitmodules
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+
+v 7.10.0 (unreleased)
+ - Ignore submodules that are defined in .gitmodules but are checked in as directories.
+ - Allow projects to be imported from Google Code.
+ - Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger)
+ - Allow users to be invited by email to join a group or project.
+ - Don't crash when project repository doesn't exist.
+ - Add config var to block auto-created LDAP users.
+ - Don't use HTML ellipsis in EmailsOnPush subject truncated commit message.
+ - Set EmailsOnPush reply-to address to committer email when enabled.
+ - Fix broken file browsing with a submodule that contains a relative link (Stan Hu)
+ - Fix persistent XSS vulnerability around profile website URLs.
+ - Fix project import URL regex to prevent arbitary local repos from being imported.
+ - Fix directory traversal vulnerability around uploads routes.
+ - Fix directory traversal vulnerability around help pages.
+ - Don't leak existence of project via search autocomplete.
+ - Don't leak existence of group or project via search.
+ - Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu)
+ - Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu)
+ - Add a rake task to check repository integrity with `git fsck`
+ - Add ability to configure Reply-To address in gitlab.yml (Stan Hu)
+ - Move current user to the top of the list in assignee/author filters (Stan Hu)
+ - Fix broken side-by-side diff view on merge request page (Stan Hu)
+ - Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu)
+ - Allow HTML tags in Markdown input
+ - Fix code unfold not working on Compare commits page (Stan Hu)
+ - Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
+ - Include missing events and fix save functionality in admin service template settings form (Stan Hu)
+ - Fix "Import projects from" button to show the correct instructions (Stan Hu)
+ - Fix dots in Wiki slugs causing errors (Stan Hu)
+ - Make maximum attachment size configurable via Application Settings (Stan Hu)
+ - Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg)
+ - Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu)
+ - Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu)
+ - Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu)
+ - enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger)
+ - Fix a link in the patch update guide
+ - Add a service to support external wikis (Hannes Rosenögger)
+ - Omit the "email patches" link and fix plain diff view for merge commits
+ - List new commits for newly pushed branch in activity view.
+ - Add sidetiq gem dependency to match EE
+ - Add changelog, license and contribution guide links to project tab bar.
+ - Improve diff UI
+ - Fix alignment of navbar toggle button (Cody Mize)
+ - Fix checkbox rendering for nested task lists
+ - Identical look of selectboxes in UI
+ - Upgrade the gitlab_git gem to version 7.1.3
+ - Move "Import existing repository by URL" option to button.
+ - Improve error message when save profile has error.
+ - Passing the name of pushed ref to CI service (requires GitLab CI 7.9+)
+ - Add location field to user profile
+ - Fix print view for markdown files and wiki pages
+ - Fix errors when deleting old backups
+ - Improve GitLab performance when working with git repositories
+ - Add tag message and last commit to tag hook (Kamil Trzciński)
+ - Restrict permissions on backup files
+ - Improve oauth accounts UI in profile page
+ - Add ability to unlink connected accounts
+ - Replace commits calendar with faster contribution calendar that includes issues and merge requests
+ - Add inifinite scroll to user page activity
+ - Don't include system notes in issue/MR comment count.
+ - Don't mark merge request as updated when merge status relative to target branch changes.
+ - Link note avatar to user.
+ - Make Git-over-SSH errors more descriptive.
+ - Fix EmailsOnPush.
+ - Refactor issue filtering
+ - AJAX selectbox for issue assignee and author filters
+ - Fix issue with missing options in issue filtering dropdown if selected one
+ - Prevent holding Control-Enter or Command-Enter from posting comment multiple times.
+ - Prevent note form from being cleared when submitting failed.
+ - Improve file icons rendering on tree (Sullivan Sénéchal)
+ - API: Add pagination to project events
+ - Get issue links in notification mail to work again.
+ - Don't show commit comment button when user is not signed in.
+ - Fix admin user projects lists.
+ - Don't leak private group existence by redirecting from namespace controller to group controller.
+ - Ability to skip some items from backup (database, respositories or uploads)
+ - Archive repositories in background worker.
+ - Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace.
+ - Project labels are now available over the API under the "tag_list" field (Cristian Medina)
+ - Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz)
+ - Fix and improve help rendering (Sullivan Sénéchal)
+ - Fix final line in EmailsOnPush email diff being rendered as error.
+ - Authometic setup GitLab CI project for forks if origin project has GitLab CI enabled
+ - Prevent duplicate Buildkite service creation.
+ - Fix git over ssh errors 'fatal: protocol error: bad line length character'
+ - Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled
+ - Bust group page project list cache when namespace name or path changes.
+ - Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded
+ - Allow user to choose a public email to show on public profile
+ - Remove truncation from issue titles on milestone page (Jason Blanchard)
+ - Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
+ - Fix merge request comments on files with multiple commits
+ - Fix Resource Owner Password Authentication Flow
+
+v 7.9.4
+ - Security: Fix project import URL regex to prevent arbitary local repos from being imported
+ - Fixed issue where only 25 commits would load in file listings
+ - Fix LDAP identities after config update
+
+v 7.9.3
+ - Contains no changes
+ - Add icons to Add dropdown items.
+ - Allow admin to create public deploy keys that are accessible to any project.
+ - Warn when gitlab-shell version doesn't match requirement.
+ - Skip email confirmation when set by admin or via LDAP.
+ - Only allow users to reference groups, projects, issues, MRs, commits they have access to.
+
+v 7.9.3
+ - Contains no changes
+
+v 7.9.2
+ - Contains no changes
+
+v 7.9.1
+ - Include missing events and fix save functionality in admin service template settings form (Stan Hu)
+ - Fix "Import projects from" button to show the correct instructions (Stan Hu)
+ - Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu)
+ - Fix for LDAP with commas in DN
+ - Fix missing events and in admin Slack service template settings form (Stan Hu)
+ - Don't show commit comment button when user is not signed in.
+ - Downgrade gemnasium-gitlab-service gem
+
+v 7.9.0
+ - Add HipChat integration documentation (Stan Hu)
+ - Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu)
+ - Fix broken email images (Hannes Rosenögger)
+ - Automatically config git if user forgot, where possible (Zeger-Jan van de Weg)
+ - Fix mass SQL statements on initial push (Hannes Rosenögger)
+ - Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu)
+ - Add comment notification events to HipChat and Slack services (Stan Hu)
+ - Add issue and merge request events to HipChat and Slack services (Stan Hu)
+ - Fix merge request URL passed to Webhooks. (Stan Hu)
+ - Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu)
+ - Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu)
+ - Move labels/milestones tabs to sidebar
+ - Upgrade Rails gem to version 4.1.9.
+ - Improve error messages for file edit failures
+ - Improve UI for commits, issues and merge request lists
+ - Fix commit comments on first line of diff not rendering in Merge Request Discussion view.
+ - Allow admins to override restricted project visibility settings.
+ - Move restricted visibility settings from gitlab.yml into the web UI.
+ - Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev)
+ - Save web edit in new branch
+ - Fix ordering of imported but unchanged projects (Marco Wessel)
+ - Mobile UI improvements: make aside content expandable
+ - Expose avatar_url in projects API
+ - Fix checkbox alignment on the application settings page.
+ - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger)
+ - Fix mass-unassignment of issues (Robert Speicher)
+ - Fix hidden diff comments in merge request discussion view
+ - Allow user confirmation to be skipped for new users via API
+ - Add a service to send updates to an Irker gateway (Romain Coltel)
+ - Add brakeman (security scanner for Ruby on Rails)
+ - Slack username and channel options
+ - Add grouped milestones from all projects to dashboard.
+ - Web hook sends pusher email as well as commiter
+ - Add Bitbucket omniauth provider.
+ - Add Bitbucket importer.
+ - Support referencing issues to a project whose name starts with a digit
+ - Condense commits already in target branch when updating merge request source branch.
+ - Send notifications and leave system comments when bulk updating issues.
+ - Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison)
+ - Move groups page from profile to dashboard
+ - Starred projects page at dashboard
+ - Blocking user does not remove him/her from project/groups but show blocked label
+ - Change subject of EmailsOnPush emails to include namespace, project and branch.
+ - Change subject of EmailsOnPush emails to include first commit message when multiple were pushed.
+ - Remove confusing footer from EmailsOnPush mail body.
+ - Add list of changed files to EmailsOnPush emails.
+ - Add option to send EmailsOnPush emails from committer email if domain matches.
+ - Add option to disable code diffs in EmailOnPush emails.
+ - Wrap commit message in EmailsOnPush email.
+ - Send EmailsOnPush emails when deleting commits using force push.
+ - Fix EmailsOnPush email comparison link to include first commit.
+ - Fix highliht of selected lines in file
+ - Reject access to group/project avatar if the user doesn't have access.
+ - Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update)
+ - Add GitLab active users count to rake gitlab:check
+ - Starred projects page at dashboard
+ - Make email display name configurable
+ - Improve json validation in hook data
+ - Use Emoji One
+ - Updated emoji help documentation to properly reference EmojiOne.
+ - Fix missing GitHub organisation repositories on import page.
+ - Added blue theme
+ - Remove annoying notice messages when create/update merge request
+ - Allow smb:// links in Markdown text.
+ - Filter merge request by title or description at Merge Requests page
+ - Block user if he/she was blocked in Active Directory
+ - Fix import pages not working after first load.
+ - Use custom LDAP label in LDAP signin form.
+ - Execute hooks and services when branch or tag is created or deleted through web interface.
+ - Block and unblock user if he/she was blocked/unblocked in Active Directory
+ - Raise recommended number of unicorn workers from 2 to 3
+ - Use same layout and interactivity for project members as group members.
+ - Prevent gitlab-shell character encoding issues by receiving its changes as raw data.
+ - Ability to unsubscribe/subscribe to issue or merge request
+ - Delete deploy key when last connection to a project is destroyed.
+ - Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther)
+ - Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup)
+ - Add canceled status for CI
+ - Send EmailsOnPush email when branch or tag is created or deleted.
+ - Faster merge request processing for large repository
+ - Prevent doubling AJAX request with each commit visit via Turbolink
+ - Prevent unnecessary doubling of js events on import pages and user calendar
+
+v 7.8.4
+ - Fix issue_tracker_id substitution in custom issue trackers
+ - Fix path and name duplication in namespaces
+
+v 7.8.3
+ - Bump version of gitlab_git fixing annotated tags without message
+
+v 7.8.2
+ - Fix service migration issue when upgrading from versions prior to 7.3
+ - Fix setting of the default use project limit via admin UI
+ - Fix showing of already imported projects for GitLab and Gitorious importers
+ - Fix response of push to repository to return "Not found" if user doesn't have access
+ - Fix check if user is allowed to view the file attachment
+ - Fix import check for case sensetive namespaces
+ - Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time.
+ - Properly handle autosave local storage exceptions.
+ - Escape wildcards when searching LDAP by username.
+
+v 7.8.1
+ - Fix run of custom post receive hooks
+ - Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3
+ - Fix the warning for LDAP users about need to set password
+ - Fix avatars which were not shown for non logged in users
+ - Fix urls for the issues when relative url was enabled
+
+v 7.8.0
+ - Fix access control and protection against XSS for note attachments and other uploads.
+ - Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
+ - Make project search case insensitive (Hannes Rosenögger)
+ - Include issue/mr participants in list of recipients for reassign/close/reopen emails
+ - Expose description in groups API
+ - Better UI for project services page
+ - Cleaner UI for web editor
+ - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
+ - Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen)
+ - View note image attachments in new tab when clicked instead of downloading them
+ - Improve sorting logic in UI and API. Explicitly define what sorting method is used by default
+ - Fix overflow at sidebar when have several items
+ - Add notes for label changes in issue and merge requests
+ - Show tags in commit view (Hannes Rosenögger)
+ - Only count a user's vote once on a merge request or issue (Michael Clarke)
+ - Increase font size when browse source files and diffs
+ - Service Templates now let you set default values for all services
+ - Create new file in empty repository using GitLab UI
+ - Ability to clone project using oauth2 token
+ - Upgrade Sidekiq gem to version 3.3.0
+ - Stop git zombie creation during force push check
+ - Show success/error messages for test setting button in services
+ - Added Rubocop for code style checks
+ - Fix commits pagination
+ - Async load a branch information at the commit page
+ - Disable blacklist validation for project names
+ - Allow configuring protection of the default branch upon first push (Marco Wessel)
+ - Add gitlab.com importer
+ - Add an ability to login with gitlab.com
+ - Add a commit calendar to the user profile (Hannes Rosenögger)
+ - Submit comment on command-enter
+ - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`.
+ - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger)
+ - Fix long broadcast message cut-off on left sidebar (Visay Keo)
+ - Add Project Avatars (Steven Thonus and Hannes Rosenögger)
+ - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
+ - Edit group members via API
+ - Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks)
+ - Add action property to merge request hook (Julien Bianchi)
+ - Remove duplicates from group milestone participants list.
+ - Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger)
+ - API: Access groups with their path (Julien Bianchi)
+ - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard)
+ - Allow notification email to be set separately from primary email.
+ - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
+ - Don't have Markdown preview fail for long comments/wiki pages.
+ - When test web hook - show error message instead of 500 error page if connection to hook url was reset
+ - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
+ - Added persistent collapse button for left side nav bar (Jason Blanchard)
+ - Prevent losing unsaved comments by automatically restoring them when comment page is loaded again.
+ - Don't allow page to be scaled on mobile.
+ - Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up.
+ - Show assignees in merge request index page (Kelvin Mutuma)
+ - Link head panel titles to relevant root page.
+ - Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S).
+ - Show users button to share their newly created public or internal projects on twitter
+ - Add quick help links to the GitLab pricing and feature comparison pages.
+ - Fix duplicate authorized applications in user profile and incorrect application client count in admin area.
+ - Make sure Markdown previews always use the same styling as the eventual destination.
+ - Remove deprecated Group#owner_id from API
+ - Show projects user contributed to on user page. Show stars near project on user page.
+ - Improve database performance for GitLab
+ - Add Asana service (Jeremy Benoist)
+ - Improve project web hooks with extra data
+
+v 7.7.2
+ - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
+ - Fix issue when LDAP user can't login with existing GitLab account
+
+v 7.7.1
+ - Improve mention autocomplete performance
+ - Show setup instructions for GitHub import if disabled
+ - Allow use http for OAuth applications
+
+v 7.7.0
+ - Import from GitHub.com feature
+ - Add Jetbrains Teamcity CI service (Jason Lippert)
+ - Mention notification level
+ - Markdown preview in wiki (Yuriy Glukhov)
+ - Raise group avatar filesize limit to 200kb
+ - OAuth applications feature
+ - Show user SSH keys in admin area
+ - Developer can push to protected branches option
+ - Set project path instead of project name in create form
+ - Block Git HTTP access after 10 failed authentication attempts
+ - Updates to the messages returned by API (sponsored by O'Reilly Media)
+ - New UI layout with side navigation
+ - Add alert message in case of outdated browser (IE < 10)
+ - Added API support for sorting projects
+ - Update gitlab_git to version 7.0.0.rc14
+ - Add API project search filter option for authorized projects
+ - Fix File blame not respecting branch selection
+ - Change some of application settings on fly in admin area UI
+ - Redesign signin/signup pages
+ - Close standard input in Gitlab::Popen.popen
+ - Trigger GitLab CI when push tags
+ - When accept merge request - do merge using sidaekiq job
+ - Enable web signups by default
+ - Fixes for diff comments: drag-n-drop images, selecting images
+ - Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update
+ - Remove password strength indicator
+
+
+
+v 7.6.0
+ - Fork repository to groups
+ - New rugged version
+ - Add CRON=1 backup setting for quiet backups
+ - Fix failing wiki restore
+ - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
+ - Monokai highlighting style now more faithful to original design (Mark Riedesel)
+ - Create project with repository in synchrony
+ - Added ability to create empty repo or import existing one if project does not have repository
+ - Reactivate highlight.js language autodetection
+ - Mobile UI improvements
+ - Change maximum avatar file size from 100KB to 200KB
+ - Strict validation for snippet file names
+ - Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada)
+ - In the docker directory is a container template based on the Omnibus packages.
+ - Update Sidekiq to version 2.17.8
+ - Add author filter to project issues and merge requests pages
+ - Atom feed for user activity
+ - Support multiple omniauth providers for the same user
+ - Rendering cross reference in issue title and tooltip for merge request
+ - Show username in comments
+ - Possibility to create Milestones or Labels when Issues are disabled
+ - Fix bug with showing gpg signature in tag
+
+v 7.5.3
+ - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
+
+v 7.5.2
+ - Don't log Sidekiq arguments by default
+ - Fix restore of wiki repositories from backups
+
+v 7.5.1
+ - Add missing timestamps to 'members' table
+
+v 7.5.0
+ - API: Add support for Hipchat (Kevin Houdebert)
+ - Add time zone configuration in gitlab.yml (Sullivan Senechal)
+ - Fix LDAP authentication for Git HTTP access
+ - Run 'GC.start' after every EmailsOnPushWorker job
+ - Fix LDAP config lookup for provider 'ldap'
+ - Drop all sequences during Postgres database restore
+ - Project title links to project homepage (Ben Bodenmiller)
+ - Add Atlassian Bamboo CI service (Drew Blessing)
+ - Mentioned @user will receive email even if he is not participating in issue or commit
+ - Session API: Use case-insensitive authentication like in UI (Andrey Krivko)
+ - Tie up loose ends with annotated tags: API & UI (Sean Edge)
+ - Return valid json for deleting branch via API (sponsored by O'Reilly Media)
+ - Expose username in project events API (sponsored by O'Reilly Media)
+ - Adds comments to commits in the API
+ - Performance improvements
+ - Fix post-receive issue for projects with deleted forks
+ - New gitlab-shell version with custom hooks support
+ - Improve code
+ - GitLab CI 5.2+ support (does not support older versions)
+ - Fixed bug when you can not push commits starting with 000000 to protected branches
+ - Added a password strength indicator
+ - Change project name and path in one form
+ - Display renamed files in diff views (Vinnie Okada)
+ - Fix raw view for public snippets
+ - Use secret token with GitLab internal API.
+ - Add missing timestamps to 'members' table
+
+v 7.4.3
+ - Fix raw snippets view
+ - Fix security issue for member api
+ - Fix buildbox integration
+
+v 7.4.2
+ - Fix internal snippet exposing for unauthenticated users
+
+v 7.4.1
+ - Fix LDAP authentication for Git HTTP access
+ - Fix LDAP config lookup for provider 'ldap'
+ - Fix public snippets
+ - Fix 500 error on projects with nested submodules
+
+v 7.4.0
+ - Refactored membership logic
+ - Improve error reporting on users API (Julien Bianchi)
+ - Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally
+ - Default branch is protected by default
+ - Increase unicorn timeout to 60 seconds
+ - Sort search autocomplete projects by stars count so most popular go first
+ - Add README to tab on project show page
+ - Do not delete tmp/repositories itself during clean-up, only its contents
+ - Support for backup uploads to remote storage
+ - Prevent notes polling when there are not notes
+ - Internal ForkService: Prepare support for fork to a given namespace
+ - API: Add support for forking a project via the API (Bernhard Kaindl)
+ - API: filter project issues by milestone (Julien Bianchi)
+ - Fail harder in the backup script
+ - Changes to Slack service structure, only webhook url needed
+ - Zen mode for wiki and milestones (Robert Schilling)
+ - Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
+ - Font Awesome 4.2 integration (Sullivan Senechal)
+ - Add Pushover service integration (Sullivan Senechal)
+ - Add select field type for services options (Sullivan Senechal)
+ - Add cross-project references to the Markdown parser (Vinnie Okada)
+ - Add task lists to issue and merge request descriptions (Vinnie Okada)
+ - Snippets can be public, internal or private
+ - Improve danger zone: ask project path to confirm data-loss action
+ - Raise exception on forgery
+ - Show build coverage in Merge Requests (requires GitLab CI v5.1)
+ - New milestone and label links on issue edit form
+ - Improved repository graphs
+ - Improve event note display in dashboard and project activity views (Vinnie Okada)
+ - Add users sorting to admin area
+ - UI improvements
+ - Fix ambiguous sha problem with mentioned commit
+ - Fixed bug with apostrophe when at mentioning users
+ - Add active directory ldap option
+ - Developers can push to wiki repo. Protected branches does not affect wiki repo any more
+ - Faster rev list
+ - Fix branch removal
+
+v 7.3.2
+ - Fix creating new file via web editor
+ - Use gitlab-shell v2.0.1
+
+v 7.3.1
+ - Fix ref parsing in Gitlab::GitAccess
+ - Fix error 500 when viewing diff on a file with changed permissions
+ - Fix adding comments to MR when source branch is master
+ - Fix error 500 when searching description contains relative link
+
+v 7.3.0
+ - Always set the 'origin' remote in satellite actions
+ - Write authorized_keys in tmp/ during tests
+ - Use sockets to connect to Redis
+ - Add dormant New Relic gem (can be enabled via environment variables)
+ - Expire Rack sessions after 1 week
+ - Cleaner signin/signup pages
+ - Improved comments UI
+ - Better search with filtering, pagination etc
+ - Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov)
+ - Prevent project stars duplication when fork project
+ - Use the default Unicorn socket backlog value of 1024
+ - Support Unix domain sockets for Redis
+ - Store session Redis keys in 'session:gitlab:' namespace
+ - Deprecate LDAP account takeover based on partial LDAP email / GitLab username match
+ - Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy)
+ - Keyboard shortcuts for productivity (Robert Schilling)
+ - API: filter issues by state (Julien Bianchi)
+ - API: filter issues by labels (Julien Bianchi)
+ - Add system hook for ssh key changes
+ - Add blob permalink link (Ciro Santilli)
+ - Create annotated tags through UI and API (Sean Edge)
+ - Snippets search (Charles Bushong)
+ - Comment new push to existing MR
+ - Add 'ci' to the blacklist of forbidden names
+ - Improve text filtering on issues page
+ - Comment & Close button
+ - Process git push --all much faster
+ - Don't allow edit of system notes
+ - Project wiki search (Ralf Seidler)
+ - Enabled Shibboleth authentication support (Matus Banas)
+ - Zen mode (fullscreen) for issues/MR/notes (Robert Schilling)
+ - Add ability to configure webhook timeout via gitlab.yml (Wes Gurney)
+ - Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media)
+ - Add Redis socket support to 'rake gitlab:shell:install'
+
v 7.2.1
- Delete orphaned labels during label migration (James Brooks)
- Security: prevent XSS with stricter MIME types for raw repo files
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f02ba2216d..3165b7379d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic
## Security vulnerability disclosure
-Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
+Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Closing policy for issues and merge requests
@@ -20,9 +20,16 @@ Please treat our volunteers with courtesy and respect, it will go a long way tow
Issues and merge requests should be in English and contain appropriate language for audiences of all ages.
+## Helping others
+
+Please help other GitLab users when you can.
+The channnels people will reach out on can be found on the [getting help page](https://about.gitlab.com/getting-help/).
+Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the irc channel.
+You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq) to help with one issue every day.
+
## Issue tracker
-To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
+To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/).
The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
@@ -37,14 +44,14 @@ Please send a merge request with a tested solution or a merge request with a fai
**[Search the issues](https://gitlab.com/gitlab-org/gitlab-ce/issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post):
1. **Summary:** Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
-1. **Steps to reproduce:** How can we reproduce the issue, preferably on the [GitLab development virtual machine with vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) (start your issue with: `vagrant destroy && vagrant up && vagrant ssh`)
+1. **Steps to reproduce:** How can we reproduce the issue
1. **Expected behavior:** Describe your issue in detail
1. **Observed behavior**
1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise.
1. **Output of checks**
* Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing
* Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md)
- * Add the last commit sha1 of the GitLab version you used to replicate the issue (obtainable from the help page)
+ * Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page)
* Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem
@@ -54,14 +61,19 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and
Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls).
+If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
+
+To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) and see [Development section](doc/development/README.md) in the help file.
+
### Merge request guidelines
If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows:
1. Fork the project on GitLab Cloud
1. Create a feature branch
-1. Write [tests](README.md#run-the-tests) and code
+1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
+1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
1. Submit a merge request (MR) to the master branch
@@ -72,34 +84,69 @@ If you can, please submit a merge request with the fix or improvements including
1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feature requests](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submission
1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
+1. Also have a look at the [shell command guidelines](doc/development/shell_commands.md) if your code reads or opens files, or handles paths to files on disk.
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
-Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a mimimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
+Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
-For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria.
+For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria.
-**Please format your merge request description as follows:**
+## Definition of done
+
+If you contribute to GitLab please know that changes involve more than just code.
+We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html).
+Please ensure you support the feature you contribute through all of these steps.
+
+1. Description explaning the relevancy (see following item)
+1. Working and clean code that is commented where needed
+1. Unit and integration tests that pass on the CI server
+1. Documented in the /doc directory
+1. Changelog entry added
+1. Reviewed and any concerns are addressed
+1. Merged by the project lead
+1. Added to the release blog article
+1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant
+1. Community questions answered
+1. Answers to questions radiated (in docs/wiki/etc.)
+
+If you add a dependency in GitLab (such as an operating system package) please consider updating the following and note the applicability of each in your merge request:
+
+1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
+1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
+1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
+1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
+1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
+1. Test suite https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/configure_a_runner_to_run_the_gitlab_ce_test_suite.md
+1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
+
+## Merge request description format
1. What does this MR do?
1. Are there points in the code the reviewer needs to double check?
1. Why was this MR needed?
1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
-1. Screenshots (If appropiate)
+1. Screenshots (if relevant)
## Contribution acceptance criteria
1. The change is as small as possible (see the above paragraph for details)
1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
-1. Can merge without problems (if not please use: `git rebase master`)
+1. All tests have to pass, if you suspect a failing CI build is unrelated to your contribution ask for tests to be restarted. See [the CI setup document](http://doc.gitlab.com/ce/development/ci_setup.html) on who you can ask for test restart.
+1. Initially contains a single commit (please use `git rebase -i` to squash commits)
+1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
1. Does not break any existing functionality
1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
+1. Migrations should do only one thing (eg: either create a table, move data to a new table or remove an old table) to aid retrying on failure
1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
-1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
-1. It conforms to the following style guides
+1. It conforms to the following style guides.
+ If your change touches a line that does not follow the style,
+ modify the entire line to follow it. This prevents linting tools from generating warnings.
+ Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications
+ may leave style non-compliant.
## Style guides
@@ -113,5 +160,20 @@ For examples of feedback on merge requests please look at already [closed merge
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style#coffeescript)
1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
+1. Interface text should be written subjectively instead of objectively. It should be the gitlab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing).
-This is also the style used by linting tools such as [Rubocop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
+This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
+
+## Code of conduct
+As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
+
+We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
+
+Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior can be
+reported by emailing contact@gitlab.com
+
+This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index fee0a2788b..097a15a2af 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-1.9.7
+2.6.2
diff --git a/Gemfile b/Gemfile
index e28ffcfc2d..56fcd8d35f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -27,22 +27,31 @@ gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
+gem 'omniauth-shibboleth'
+gem 'omniauth-kerberos'
+gem 'omniauth-gitlab'
+gem 'omniauth-bitbucket'
+gem 'doorkeeper', '2.1.3'
+gem "rack-oauth2", "~> 1.0.5"
+
+# Browser detection
+gem "browser"
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 6.0'
+gem "gitlab_git", '~> 7.1.10'
# Ruby/Rack Git Smart-HTTP Server Handler
-gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
+gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# LDAP Auth
-gem 'gitlab_omniauth-ldap', '1.0.4', require: "omniauth-ldap"
+gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
# Git Wiki
-gem 'gollum-lib', '~> 3.0.0'
+gem 'gollum-lib', '~> 4.0.2'
# Language detection
-gem "gitlab-linguist", "~> 3.0.0", require: "linguist"
+gem "gitlab-linguist", "~> 3.0.1", require: "linguist"
# API
gem "grape", "~> 0.6.1"
@@ -69,8 +78,8 @@ gem "carrierwave"
gem 'dropzonejs-rails'
# for aws storage
-gem "fog", "~> 1.14", group: :aws
-gem "unf", group: :aws
+gem "fog", "~> 1.14"
+gem "unf"
# Authorization
gem "six"
@@ -78,14 +87,17 @@ gem "six"
# Seed data
gem "seed-fu"
+# Markup pipeline for GitLab
+gem 'html-pipeline-gitlab', '~> 0.1'
+
# Markdown to HTML
gem "github-markup"
# Required markup gems by github-markdown
-gem 'redcarpet', '~> 2.2.2'
+gem 'redcarpet', '~> 3.2.3'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
-gem 'org-ruby'
+gem 'org-ruby', '= 0.9.12'
gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4'
@@ -103,12 +115,13 @@ end
gem "state_machine"
# Issue tags
-gem "acts-as-taggable-on"
+gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
-gem 'sidekiq', '2.17.0'
+gem 'sidekiq', '~> 3.3'
+gem 'sidetiq', '0.6.3'
# HTTP requests
gem "httparty"
@@ -130,7 +143,7 @@ gem "redis-rails"
gem 'tinder', '~> 1.9.2'
# HipChat integration
-gem "hipchat", "~> 0.14.0"
+gem "hipchat", "~> 1.4.0"
# Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 0.4.2"
@@ -139,11 +152,17 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2"
gem "gemnasium-gitlab-service", "~> 0.2"
# Slack integration
-gem "slack-notifier", "~> 0.3.2"
+gem "slack-notifier", "~> 1.0.0"
+
+# Asana integration
+gem 'asana', '~> 0.0.6'
# d3
gem "d3_rails", "~> 3.1.4"
+#cal-heatmap
+gem "cal-heatmap-rails", "~> 0.0.1"
+
# underscore-rails
gem "underscore-rails", "~> 1.4.4"
@@ -156,13 +175,15 @@ gem "rack-attack"
# Ace editor
gem 'ace-rails-ap'
-# Semantic UI Sass for Sidebar
-gem 'semantic-ui-sass', '~> 0.16.1.0'
+# Keyboard shortcuts
+gem 'mousetrap-rails'
+
+# Detect and convert string character encoding
+gem 'charlock_holmes'
gem "sass-rails", '~> 4.0.2'
gem "coffee-rails"
gem "uglifier"
-gem "therubyracer"
gem 'turbolinks'
gem 'jquery-turbolinks'
@@ -173,14 +194,16 @@ gem "jquery-ui-rails"
gem "jquery-scrollto-rails"
gem "raphael-rails", "~> 2.1.2"
gem 'bootstrap-sass', '~> 3.0'
-gem "font-awesome-rails", '~> 3.2'
-gem "gitlab_emoji", "~> 0.0.1.1"
+gem "font-awesome-rails", '~> 4.2'
+gem "gitlab_emoji", "~> 0.1"
gem "gon", '~> 5.0.0'
gem 'nprogress-rails'
gem 'request_store'
gem "virtus"
+gem 'addressable'
group :development do
+ gem 'brakeman', require: false
gem "annotate", "~> 2.6.0.beta2"
gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1'
@@ -190,8 +213,6 @@ group :development do
gem 'better_errors'
gem 'binding_of_caller'
- gem 'rails_best_practices'
-
# Docs generator
gem "sdoc"
@@ -201,11 +222,12 @@ end
group :development, :test do
gem 'coveralls', require: false
+ gem 'rubocop', '0.28.0', require: false
# gem 'rails-dev-tweaks'
gem 'spinach-rails'
- gem "rspec-rails"
+ gem "rspec-rails", '2.99'
gem "capybara", '~> 2.2.1'
- gem "pry"
+ gem "pry-rails"
gem "awesome_print"
gem "database_cleaner"
gem "launchy"
@@ -231,14 +253,16 @@ group :development, :test do
gem 'jasmine', '2.0.2'
- gem "spring", '1.1.1'
- gem "spring-commands-rspec", '1.0.1'
+ gem "spring", '~> 1.3.1'
+ gem "spring-commands-rspec", '1.0.4'
gem "spring-commands-spinach", '1.0.0'
+
+ gem "byebug"
end
group :test do
gem "simplecov", require: false
- gem "shoulda-matchers", "~> 2.1.0"
+ gem "shoulda-matchers", "~> 2.7.0"
gem 'email_spec'
gem "webmock"
gem 'test_after_commit'
@@ -247,3 +271,8 @@ end
group :production do
gem "gitlab_meta", '7.0'
end
+
+gem "newrelic_rpm"
+
+gem 'octokit', '3.7.0'
+gem "rugments"
diff --git a/Gemfile.lock b/Gemfile.lock
index 205599e898..2fd59857be 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,40 +3,53 @@ GEM
specs:
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
- actionmailer (4.1.1)
- actionpack (= 4.1.1)
- actionview (= 4.1.1)
- mail (~> 2.5.4)
- actionpack (4.1.1)
- actionview (= 4.1.1)
- activesupport (= 4.1.1)
+ actionmailer (4.1.9)
+ actionpack (= 4.1.9)
+ actionview (= 4.1.9)
+ mail (~> 2.5, >= 2.5.4)
+ actionpack (4.1.9)
+ actionview (= 4.1.9)
+ activesupport (= 4.1.9)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
- actionview (4.1.1)
- activesupport (= 4.1.1)
+ actionview (4.1.9)
+ activesupport (= 4.1.9)
builder (~> 3.1)
erubis (~> 2.7.0)
- activemodel (4.1.1)
- activesupport (= 4.1.1)
+ activemodel (4.1.9)
+ activesupport (= 4.1.9)
builder (~> 3.1)
- activerecord (4.1.1)
- activemodel (= 4.1.1)
- activesupport (= 4.1.1)
+ activerecord (4.1.9)
+ activemodel (= 4.1.9)
+ activesupport (= 4.1.9)
arel (~> 5.0.0)
- activesupport (4.1.1)
+ activeresource (4.0.0)
+ activemodel (~> 4.0)
+ activesupport (~> 4.0)
+ rails-observers (~> 0.1.1)
+ activesupport (4.1.9)
i18n (~> 0.6, >= 0.6.9)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
- acts-as-taggable-on (2.4.1)
- rails (>= 3, < 5)
+ acts-as-taggable-on (3.5.0)
+ activerecord (>= 3.2, < 5)
addressable (2.3.5)
annotate (2.6.0)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
arel (5.0.1.20140414130214)
+ asana (0.0.6)
+ activeresource (>= 3.2.3)
asciidoctor (0.1.4)
+ ast (2.0.0)
+ astrolabe (1.3.0)
+ parser (>= 2.2.0.pre.3, < 3.0)
+ attr_required (1.0.0)
+ autoprefixer-rails (5.1.6)
+ execjs
+ json
awesome_print (1.2.0)
axiom-types (0.0.5)
descendants_tracker (~> 0.0.1)
@@ -47,9 +60,25 @@ GEM
erubis (>= 2.6.6)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
- bootstrap-sass (3.0.3.0)
- sass (~> 3.2)
+ bootstrap-sass (3.3.3)
+ autoprefixer-rails (>= 5.0.0.1)
+ sass (>= 3.2.19)
+ brakeman (3.0.1)
+ erubis (~> 2.6)
+ fastercsv (~> 1.5)
+ haml (>= 3.0, < 5.0)
+ highline (~> 1.6.20)
+ multi_json (~> 1.2)
+ ruby2ruby (~> 2.1.1)
+ ruby_parser (~> 3.5.0)
+ sass (~> 3.0)
+ terminal-table (~> 1.4)
+ browser (0.7.2)
builder (3.2.2)
+ byebug (3.2.0)
+ columnize (~> 0.8)
+ debugger-linecache (~> 1.2)
+ cal-heatmap-rails (0.0.1)
capybara (2.2.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
@@ -60,12 +89,10 @@ GEM
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
- celluloid (0.15.2)
- timers (~> 1.1.0)
+ celluloid (0.16.0)
+ timers (~> 4.0.0)
charlock_holmes (0.6.9.4)
cliver (0.3.2)
- code_analyzer (0.4.3)
- sexp_processor
coderay (1.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
@@ -78,7 +105,8 @@ GEM
coffee-script-source (1.6.3)
colored (1.2)
colorize (0.5.8)
- connection_pool (1.2.0)
+ columnize (0.9.0)
+ connection_pool (2.1.0)
coveralls (0.7.0)
multi_json (~> 1.3)
rest-client
@@ -93,6 +121,7 @@ GEM
daemons (1.1.9)
database_cleaner (1.3.0)
debug_inspector (0.0.2)
+ debugger-linecache (1.2.0)
default_value_for (3.0.0)
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.3)
@@ -106,21 +135,21 @@ GEM
devise (~> 3.2)
diff-lcs (1.2.5)
diffy (3.0.3)
- docile (1.1.1)
+ docile (1.1.5)
+ doorkeeper (2.1.3)
+ railties (>= 3.2)
dotenv (0.9.0)
dropzonejs-rails (0.4.14)
rails (> 3.1)
email_spec (1.5.0)
launchy (~> 2.1)
mail (~> 2.2)
- emoji (1.0.1)
- json
enumerize (0.7.0)
activesupport (>= 3.2)
equalizer (0.0.8)
erubis (2.7.0)
escape_utils (0.2.4)
- eventmachine (1.0.3)
+ eventmachine (1.0.4)
excon (0.32.1)
execjs (2.0.2)
expression_parser (0.9.0)
@@ -133,6 +162,7 @@ GEM
multipart-post (~> 1.2.0)
faraday_middleware (0.9.0)
faraday (>= 0.7.4, < 0.9)
+ fastercsv (1.5.5)
ffaker (1.22.1)
ffi (1.9.3)
fog (1.21.0)
@@ -152,50 +182,54 @@ GEM
net-ssh (>= 2.1.3)
fog-json (1.0.0)
multi_json (~> 1.0)
- font-awesome-rails (3.2.1.3)
+ font-awesome-rails (4.2.0.0)
railties (>= 3.2, < 5.0)
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
formatador (0.2.4)
- gemnasium-gitlab-service (0.2.2)
- rugged (~> 0.19)
+ gemnasium-gitlab-service (0.2.6)
+ rugged (~> 0.21)
+ gemojione (2.0.0)
+ json
gherkin-ruby (0.3.1)
racc
- github-markup (1.1.0)
+ github-markup (1.3.1)
+ posix-spawn (~> 0.3.8)
gitlab-flowdock-git-hook (0.4.2.2)
gitlab-grit (>= 2.4.1)
multi_json
- gitlab-grack (2.0.0.pre)
+ gitlab-grack (2.0.2)
rack (~> 1.5.1)
- gitlab-grit (2.6.10)
+ gitlab-grit (2.7.2)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
posix-spawn (~> 0.3)
- gitlab-linguist (3.0.0)
+ gitlab-linguist (3.0.1)
charlock_holmes (~> 0.6.6)
escape_utils (~> 0.2.4)
mime-types (~> 1.19)
- gitlab_emoji (0.0.1.1)
- emoji (~> 1.0.1)
- gitlab_git (6.2.1)
+ gitlab_emoji (0.1.0)
+ gemojione (~> 2.0)
+ gitlab_git (7.1.10)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
- gitlab-grit (~> 2.6)
gitlab-linguist (~> 3.0)
- rugged (~> 0.21.0)
+ rugged (~> 0.21.2)
gitlab_meta (7.0)
- gitlab_omniauth-ldap (1.0.4)
- net-ldap (~> 0.3.1)
+ gitlab_omniauth-ldap (1.2.1)
+ net-ldap (~> 0.9)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
- rubyntlm (~> 0.1.1)
- gollum-lib (3.0.0)
- github-markup (~> 1.1.0)
- gitlab-grit (~> 2.6.5)
- nokogiri (~> 1.6.1)
- rouge (~> 1.3.3)
+ rubyntlm (~> 0.3)
+ gollum-grit_adapter (0.1.3)
+ gitlab-grit (~> 2.7, >= 2.7.1)
+ gollum-lib (4.0.2)
+ github-markup (~> 1.3.1)
+ gollum-grit_adapter (~> 0.1, >= 0.1.1)
+ nokogiri (~> 1.6.4)
+ rouge (~> 1.7.4)
sanitize (~> 2.1.0)
stringex (~> 2.5.1)
gon (5.0.1)
@@ -235,16 +269,28 @@ GEM
haml (>= 3.1, < 5.0)
railties (>= 4.0.1)
hashie (2.1.2)
+ highline (1.6.21)
hike (1.2.3)
- hipchat (0.14.0)
- httparty
+ hipchat (1.4.0)
httparty
+ hitimes (1.2.2)
+ html-pipeline (1.11.0)
+ activesupport (>= 2)
+ nokogiri (~> 1.4)
+ html-pipeline-gitlab (0.2.0)
+ actionpack (~> 4)
+ gitlab_emoji (~> 0.1)
+ html-pipeline (~> 1.11.0)
+ mime-types
+ sanitize (~> 2.1)
http_parser.rb (0.5.3)
httparty (0.13.0)
json (~> 1.8)
multi_xml (>= 0.5.2)
httpauth (0.2.1)
- i18n (0.6.11)
+ httpclient (2.5.3.3)
+ i18n (0.7.0)
+ ice_cube (0.11.1)
ice_nine (0.10.0)
jasmine (2.0.2)
jasmine-core (~> 2.0.0)
@@ -263,40 +309,40 @@ GEM
turbolinks
jquery-ui-rails (4.2.1)
railties (>= 3.2.16)
- json (1.8.1)
+ json (1.8.2)
jwt (0.1.13)
multi_json (>= 1.5)
kaminari (0.15.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
- kgio (2.8.1)
+ kgio (2.9.2)
launchy (2.4.2)
addressable (~> 2.3)
letter_opener (1.1.2)
launchy (~> 2.2)
- libv8 (3.16.14.3)
listen (2.3.1)
celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
lumberjack (1.0.4)
- mail (2.5.4)
- mime-types (~> 1.16)
- treetop (~> 1.4.8)
+ mail (2.6.3)
+ mime-types (>= 1.16, < 3)
method_source (0.8.2)
mime-types (1.25.1)
- mini_portile (0.6.0)
+ mini_portile (0.6.1)
minitest (5.3.5)
+ mousetrap-rails (1.4.6)
multi_json (1.10.1)
multi_xml (0.5.5)
multipart-post (1.2.0)
mysql2 (0.3.16)
- net-ldap (0.3.1)
+ net-ldap (0.11)
net-scp (1.1.2)
net-ssh (>= 2.6.5)
net-ssh (2.8.0)
- nokogiri (1.6.2.1)
- mini_portile (= 0.6.0)
+ newrelic_rpm (3.9.4.245)
+ nokogiri (1.6.5)
+ mini_portile (~> 0.6.0)
nprogress-rails (0.1.2.3)
oauth (0.4.7)
oauth2 (0.8.1)
@@ -305,27 +351,45 @@ GEM
jwt (~> 0.1.4)
multi_json (~> 1.0)
rack (~> 1.2)
+ octokit (3.7.0)
+ sawyer (~> 0.6.0, >= 0.5.3)
omniauth (1.1.4)
hashie (>= 1.2, < 3)
rack
+ omniauth-bitbucket (0.0.2)
+ multi_json (~> 1.7)
+ omniauth (~> 1.1)
+ omniauth-oauth (~> 1.0)
omniauth-github (1.1.1)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
+ omniauth-gitlab (1.0.0)
+ omniauth (~> 1.0)
+ omniauth-oauth2 (~> 1.0)
omniauth-google-oauth2 (0.2.5)
omniauth (> 1.0)
omniauth-oauth2 (~> 1.1)
+ omniauth-kerberos (0.2.0)
+ omniauth-multipassword
+ timfel-krb5-auth (~> 0.8)
+ omniauth-multipassword (0.4.1)
+ omniauth (~> 1.0)
omniauth-oauth (1.0.1)
oauth
omniauth (~> 1.0)
omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
+ omniauth-shibboleth (1.1.1)
+ omniauth (>= 1.0.0)
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
- org-ruby (0.9.8)
+ org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
+ parser (2.2.0.2)
+ ast (>= 1.1, < 3.0)
pg (0.15.1)
phantomjs (1.9.2.0)
poltergeist (1.5.1)
@@ -333,12 +397,14 @@ GEM
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
- polyglot (0.3.4)
posix-spawn (0.3.9)
+ powerpack (0.0.9)
pry (0.9.12.4)
coderay (~> 1.0)
method_source (~> 0.8)
slop (~> 3.4)
+ pry-rails (0.3.2)
+ pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3)
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
@@ -346,53 +412,53 @@ GEM
rack (1.5.2)
rack-accept (0.4.5)
rack (>= 0.4)
- rack-attack (2.3.0)
+ rack-attack (4.2.0)
rack
rack-cors (0.2.9)
rack-mini-profiler (0.9.0)
rack (>= 1.1.3)
rack-mount (0.8.3)
rack (>= 1.0.0)
+ rack-oauth2 (1.0.8)
+ activesupport (>= 2.3)
+ attr_required (>= 0.0.5)
+ httpclient (>= 2.2.0.2)
+ multi_json (>= 1.3.6)
+ rack (>= 1.1)
rack-protection (1.5.1)
rack
- rack-test (0.6.2)
+ rack-test (0.6.3)
rack (>= 1.0)
- rails (4.1.1)
- actionmailer (= 4.1.1)
- actionpack (= 4.1.1)
- actionview (= 4.1.1)
- activemodel (= 4.1.1)
- activerecord (= 4.1.1)
- activesupport (= 4.1.1)
+ rails (4.1.9)
+ actionmailer (= 4.1.9)
+ actionpack (= 4.1.9)
+ actionview (= 4.1.9)
+ activemodel (= 4.1.9)
+ activerecord (= 4.1.9)
+ activesupport (= 4.1.9)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.1.1)
+ railties (= 4.1.9)
sprockets-rails (~> 2.0)
+ rails-observers (0.1.2)
+ activemodel (~> 4.0)
rails_autolink (1.1.6)
rails (> 3.1)
- rails_best_practices (1.14.4)
- activesupport
- awesome_print
- code_analyzer (>= 0.4.3)
- colored
- erubis
- i18n
- require_all
- ruby-progressbar
- railties (4.1.1)
- actionpack (= 4.1.1)
- activesupport (= 4.1.1)
+ railties (4.1.9)
+ actionpack (= 4.1.9)
+ activesupport (= 4.1.9)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- raindrops (0.12.0)
- rake (10.3.2)
+ rainbow (2.0.0)
+ raindrops (0.13.0)
+ rake (10.4.2)
raphael-rails (2.1.2)
rb-fsevent (0.9.3)
rb-inotify (0.9.2)
ffi (>= 0.5.0)
rdoc (3.12.2)
json (~> 1.4)
- redcarpet (2.2.2)
- redis (3.0.6)
+ redcarpet (3.2.3)
+ redis (3.1.0)
redis-actionpack (4.0.0)
actionpack (~> 4)
redis-rack (~> 1.5.0)
@@ -400,8 +466,8 @@ GEM
redis-activesupport (4.0.0)
activesupport (~> 4)
redis-store (~> 1.1.0)
- redis-namespace (1.4.1)
- redis (~> 3.0.4)
+ redis-namespace (1.5.1)
+ redis (~> 3.0, >= 3.0.4)
redis-rack (1.5.0)
rack (~> 1.5)
redis-store (~> 1.1.0)
@@ -411,32 +477,46 @@ GEM
redis-store (~> 1.1.0)
redis-store (1.1.4)
redis (>= 2.2)
- ref (1.0.5)
request_store (1.0.5)
- require_all (1.3.2)
rest-client (1.6.7)
mime-types (>= 1.16)
rinku (1.7.3)
- rouge (1.3.3)
- rspec (2.14.1)
- rspec-core (~> 2.14.0)
- rspec-expectations (~> 2.14.0)
- rspec-mocks (~> 2.14.0)
- rspec-core (2.14.7)
- rspec-expectations (2.14.4)
+ rouge (1.7.7)
+ rspec (2.99.0)
+ rspec-core (~> 2.99.0)
+ rspec-expectations (~> 2.99.0)
+ rspec-mocks (~> 2.99.0)
+ rspec-collection_matchers (1.1.2)
+ rspec-expectations (>= 2.99.0.beta1)
+ rspec-core (2.99.2)
+ rspec-expectations (2.99.2)
diff-lcs (>= 1.1.3, < 2.0)
- rspec-mocks (2.14.4)
- rspec-rails (2.14.0)
+ rspec-mocks (2.99.3)
+ rspec-rails (2.99.0)
actionpack (>= 3.0)
+ activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
- rspec-core (~> 2.14.0)
- rspec-expectations (~> 2.14.0)
- rspec-mocks (~> 2.14.0)
- ruby-progressbar (1.2.0)
- rubyntlm (0.1.1)
+ rspec-collection_matchers
+ rspec-core (~> 2.99.0)
+ rspec-expectations (~> 2.99.0)
+ rspec-mocks (~> 2.99.0)
+ rubocop (0.28.0)
+ astrolabe (~> 1.3)
+ parser (>= 2.2.0.pre.7, < 3.0)
+ powerpack (~> 0.0.6)
+ rainbow (>= 1.99.1, < 3.0)
+ ruby-progressbar (~> 1.4)
+ ruby-progressbar (1.7.1)
+ ruby2ruby (2.1.3)
+ ruby_parser (~> 3.1)
+ sexp_processor (~> 4.0)
+ ruby_parser (3.5.0)
+ sexp_processor (~> 4.1)
+ rubyntlm (0.5.0)
rubypants (0.2.0)
- rugged (0.21.0)
+ rugged (0.21.4)
+ rugments (1.0.0.beta6)
safe_yaml (0.9.7)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
@@ -446,6 +526,9 @@ GEM
sass (~> 3.2.0)
sprockets (~> 2.8, <= 2.11.0)
sprockets-rails (~> 2.0)
+ sawyer (0.6.0)
+ addressable (~> 2.3.5)
+ faraday (~> 0.8, < 0.10)
sdoc (0.3.20)
json (>= 1.1.3)
rdoc (~> 3.10)
@@ -454,20 +537,22 @@ GEM
activesupport (>= 3.1, < 4.2)
select2-rails (3.5.2)
thor (~> 0.14)
- semantic-ui-sass (0.16.1.0)
- sass (~> 3.2)
settingslogic (2.0.9)
- sexp_processor (4.4.0)
- shoulda-matchers (2.1.0)
+ sexp_processor (4.4.5)
+ shoulda-matchers (2.7.0)
activesupport (>= 3.0.0)
- sidekiq (2.17.0)
- celluloid (>= 0.15.2)
- connection_pool (>= 1.0.0)
+ sidekiq (3.3.0)
+ celluloid (>= 0.16.0)
+ connection_pool (>= 2.0.0)
json
- redis (>= 3.0.4)
+ redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
+ sidetiq (0.6.3)
+ celluloid (>= 0.14.1)
+ ice_cube (= 0.11.1)
+ sidekiq (>= 3.0.0)
simple_oauth (0.1.9)
- simplecov (0.8.2)
+ simplecov (0.9.0)
docile (~> 1.1.0)
multi_json
simplecov-html (~> 0.8.0)
@@ -477,11 +562,11 @@ GEM
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
six (0.2.0)
- slack-notifier (0.3.2)
+ slack-notifier (1.0.0)
slim (2.0.2)
temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1)
- slop (3.4.7)
+ slop (3.6.0)
spinach (0.8.7)
colorize (= 0.5.8)
gherkin-ruby (>= 0.3.1)
@@ -489,8 +574,8 @@ GEM
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
- spring (1.1.1)
- spring-commands-rspec (1.0.1)
+ spring (1.3.3)
+ spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
spring-commands-spinach (1.0.0)
spring (>= 0.9.1)
@@ -499,28 +584,28 @@ GEM
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
- sprockets-rails (2.1.3)
+ sprockets-rails (2.2.4)
actionpack (>= 3.0)
activesupport (>= 3.0)
- sprockets (~> 2.8)
+ sprockets (>= 2.8, < 4.0)
stamp (0.5.0)
state_machine (1.2.0)
- stringex (2.5.1)
+ stringex (2.5.2)
temple (0.6.7)
term-ansicolor (1.2.2)
tins (~> 0.8)
+ terminal-table (1.4.5)
test_after_commit (0.2.2)
- therubyracer (0.12.0)
- libv8 (~> 3.16.14.0)
- ref
thin (1.6.1)
daemons (>= 1.0.9)
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
thor (0.19.1)
- thread_safe (0.3.4)
+ thread_safe (0.3.5)
tilt (1.4.1)
- timers (1.1.0)
+ timers (4.0.1)
+ hitimes
+ timfel-krb5-auth (0.8.3)
tinder (1.9.3)
eventmachine (~> 1.0)
faraday (~> 0.8)
@@ -531,9 +616,6 @@ GEM
multi_json (~> 1.7)
twitter-stream (~> 0.1)
tins (0.13.1)
- treetop (1.4.15)
- polyglot
- polyglot (>= 0.3.1)
turbolinks (2.0.0)
coffee-rails
twitter-stream (0.1.16)
@@ -555,7 +637,7 @@ GEM
raindrops (~> 0.7)
unicorn-worker-killer (0.4.2)
unicorn (~> 4)
- version_sorter (1.1.0)
+ version_sorter (2.0.0)
virtus (1.0.1)
axiom-types (~> 0.0.5)
coercible (~> 1.0)
@@ -580,15 +662,22 @@ PLATFORMS
DEPENDENCIES
RedCloth
ace-rails-ap
- acts-as-taggable-on
+ acts-as-taggable-on (~> 3.4)
+ addressable
annotate (~> 2.6.0.beta2)
+ asana (~> 0.0.6)
asciidoctor (= 0.1.4)
awesome_print
better_errors
binding_of_caller
bootstrap-sass (~> 3.0)
+ brakeman
+ browser
+ byebug
+ cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.2.1)
carrierwave
+ charlock_holmes
coffee-rails
colored
coveralls
@@ -599,24 +688,25 @@ DEPENDENCIES
devise (= 3.2.4)
devise-async (= 0.9.0)
diffy (~> 3.0.3)
+ doorkeeper (= 2.1.3)
dropzonejs-rails
email_spec
enumerize
factory_girl_rails
ffaker
fog (~> 1.14)
- font-awesome-rails (~> 3.2)
+ font-awesome-rails (~> 4.2)
foreman
gemnasium-gitlab-service (~> 0.2)
github-markup
gitlab-flowdock-git-hook (~> 0.4.2)
- gitlab-grack (~> 2.0.0.pre)
- gitlab-linguist (~> 3.0.0)
- gitlab_emoji (~> 0.0.1.1)
- gitlab_git (~> 6.0)
+ gitlab-grack (~> 2.0.2)
+ gitlab-linguist (~> 3.0.1)
+ gitlab_emoji (~> 0.1)
+ gitlab_git (~> 7.1.10)
gitlab_meta (= 7.0)
- gitlab_omniauth-ldap (= 1.0.4)
- gollum-lib (~> 3.0.0)
+ gitlab_omniauth-ldap (= 1.2.1)
+ gollum-lib (~> 4.0.2)
gon (~> 5.0.0)
grape (~> 0.6.1)
grape-entity (~> 0.4.2)
@@ -624,7 +714,8 @@ DEPENDENCIES
guard-rspec
guard-spinach
haml-rails
- hipchat (~> 0.14.0)
+ hipchat (~> 1.4.0)
+ html-pipeline-gitlab (~> 0.1)
httparty
jasmine (= 2.0.2)
jquery-atwho-rails (~> 0.3.3)
@@ -636,53 +727,61 @@ DEPENDENCIES
launchy
letter_opener
minitest (~> 5.3.0)
+ mousetrap-rails
mysql2
+ newrelic_rpm
nprogress-rails
+ octokit (= 3.7.0)
omniauth (~> 1.1.3)
+ omniauth-bitbucket
omniauth-github
+ omniauth-gitlab
omniauth-google-oauth2
+ omniauth-kerberos
+ omniauth-shibboleth
omniauth-twitter
- org-ruby
+ org-ruby (= 0.9.12)
pg
poltergeist (~> 1.5.1)
- pry
+ pry-rails
quiet_assets (~> 1.0.1)
rack-attack
rack-cors
rack-mini-profiler
+ rack-oauth2 (~> 1.0.5)
rails (~> 4.1.0)
rails_autolink (~> 1.1)
- rails_best_practices
raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
rdoc (~> 3.6)
- redcarpet (~> 2.2.2)
+ redcarpet (~> 3.2.3)
redis-rails
request_store
- rspec-rails
+ rspec-rails (= 2.99)
+ rubocop (= 0.28.0)
+ rugments
sanitize (~> 2.0)
sass-rails (~> 4.0.2)
sdoc
seed-fu
select2-rails
- semantic-ui-sass (~> 0.16.1.0)
settingslogic
- shoulda-matchers (~> 2.1.0)
- sidekiq (= 2.17.0)
+ shoulda-matchers (~> 2.7.0)
+ sidekiq (~> 3.3)
+ sidetiq (= 0.6.3)
simplecov
sinatra
six
- slack-notifier (~> 0.3.2)
+ slack-notifier (~> 1.0.0)
slim
spinach-rails
- spring (= 1.1.1)
- spring-commands-rspec (= 1.0.1)
+ spring (~> 1.3.1)
+ spring-commands-rspec (= 1.0.4)
spring-commands-spinach (= 1.0.0)
stamp
state_machine
test_after_commit
- therubyracer
thin
tinder (~> 1.9.2)
turbolinks
diff --git a/Guardfile b/Guardfile
index e19a312377..68ac3232b0 100644
--- a/Guardfile
+++ b/Guardfile
@@ -1,7 +1,7 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
-guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_pass: false do
+guard 'rspec', cmd: "spring rspec", all_on_start: false, all_after_pass: false do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
@@ -19,7 +19,7 @@ guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_p
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
end
-guard 'spinach' do
+guard 'spinach', command_prefix: 'spring' do
watch(%r|^features/(.*)\.feature|)
watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
"features/#{m[1]}#{m[2]}.feature"
diff --git a/LICENSE b/LICENSE
index d11b8730bf..d8cb29f363 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2014 GitLab B.V.
+Copyright (c) 2011-2015 GitLab B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index 19200fef38..d3d3667069 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -2,7 +2,7 @@
GitLab is a fast moving and evolving project. We currently don't have the resources to support many releases concurrently. We support exactly one stable release at any given time.
-GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: `(Major).(Minor).(Patch)`.
+GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: `(Major).(Minor).(Patch)` in a [pragmatic way](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e).
- **Major version**: Whenever there is something significant or any backwards incompatible changes are introduced to the public API.
- **Minor version**: When new, backwards compatible functionality is introduced to the public API or a minor feature is introduced, or when a set of smaller features is rolled out.
diff --git a/PROCESS.md b/PROCESS.md
index c986013e2f..1b6b3e7d32 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
- Responds to merge requests the issue team mentions them in and monitors for new merge requests
- Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.)
- Mark merge requests 'ready-for-merge' when they meet the contribution guidelines
-- Mention developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/)
+- Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/)
- Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team
@@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people
-The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
+The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://about.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
## Workflow labels
@@ -71,7 +71,7 @@ Thanks for the issue report. Please reformat your issue to conform to the issue
### Feature requests
-Thank you for your interest in improving GitLab. We don't use the issue tracker for feature requests. Things that are wrong but are not a regression compared to older versions of GitLab are considered feature requests and not issues. Please use the [feature request forum](http://feedback.gitlab.com/) for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
+Thank you for your interest in improving GitLab. We don't use the issue tracker for feature requests. Things that are wrong but are not a regression compared to older versions of GitLab are considered feature requests and not issues. Please use the \[feature request forum\]\(http://feedback.gitlab.com/) for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
### Issue report for old version
@@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver
### Support requests and configuration questions
-Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
+Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://about.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
### Code format
@@ -87,7 +87,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea
### Issue fixed in newer version
-Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://github.com/gitlabhq/gitlabhq/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Improperly formatted merge request
@@ -104,3 +104,10 @@ This merge request has been closed because a request for more information has no
### Accepting merge requests
Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this? If so, can you make a comment with a link to it? Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab. You might be asked to make changes and even after implementing them your feature might still be declined. If you want to reduce the chance of this happening please have a discussion in the forum first.
+
+### Only accepting merge requests with green tests
+
+We can only accept a merge request if all the tests are green. I've just
+restarted the build. When the tests are still not passing after this restart and
+you're sure that is does not have anything to do with your code changes, please
+rebase with master to see if that solves the issue.
diff --git a/Procfile b/Procfile
index a5693f8dbc..799b92729f 100644
--- a/Procfile
+++ b/Procfile
@@ -1,2 +1,2 @@
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
-worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default,gitlab_shell
+worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default
diff --git a/README.md b/README.md
index f5f7a8aad4..0563ceca40 100644
--- a/README.md
+++ b/README.md
@@ -1,124 +1,90 @@
-# GitLab
+# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
## Open source software to collaborate on code
-![logo](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/gitlab_logo.png)
-
-![animated-screenshots](https://gist.github.com/fnkr/2f9badd56bfe0ed04ee7/raw/4f48806fbae97f556c2f78d8c2d299c04500cb0d/compiled.gif)
+![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif)
- Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests
- Each project can also have an issue tracker and a wiki
- Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
- Completely free and open source (MIT Expat license)
-- Powered by Ruby on Rails
+- Powered by [Ruby on Rails](https://github.com/rails/rails)
+
+## Editions
+
+There are two editions of GitLab.
+*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license.
+
+*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users.
+To get access to the EE and support please [become a subscriber](https://about.gitlab.com/pricing/).
## Canonical source
-- The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
+The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
## Code status
- [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
-- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
-- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
+- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
-- [![PullReview stats](https://www.pullreview.com/gitlab/gitlab-org/gitlab-ce/badges/master.svg?)](https://www.pullreview.com/gitlab.gitlab.com/gitlab-org/gitlab-ce/reviews/master)
+- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
## Website
-On [www.gitlab.com](https://www.gitlab.com/) you can find more information about:
+On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
-- [Subscriptions](https://www.gitlab.com/subscription/)
-- [Consultancy](https://www.gitlab.com/consultancy/)
-- [Community](https://www.gitlab.com/community/)
-- [Hosted GitLab.com](https://www.gitlab.com/gitlab-com/) use GitLab as a free service
-- [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
-- [GitLab CI](https://www.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
-
-## Third-party applications
-
-Access GitLab from multiple platforms with applications below.
-These applications are maintained by contributors, GitLab B.V. does not offer support for them.
-
-- [iPhone app](http://gitlabcontrol.com/)
-- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
-- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
-- [Command line client](https://github.com/drewblessing/gitlab-cli)
-- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
+- [Subscriptions](https://about.gitlab.com/subscription/)
+- [Consultancy](https://about.gitlab.com/consultancy/)
+- [Community](https://about.gitlab.com/community/)
+- [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service
+- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
+- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Requirements
-- Ubuntu/Debian/CentOS/RHEL**
-- ruby 2.0+
-- git 1.7.10+
-- redis 2.0+
+GitLab requires the following software:
+
+- Ubuntu/Debian/CentOS/RHEL
+- Ruby (MRI) 2.0 or 2.1
+- Git 1.7.10+
+- Redis 2.0+
- MySQL or PostgreSQL
-** More details are in the [requirements doc](doc/install/requirements.md).
+Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems.
## Installation
-Please see [the installation page on the GitLab website](https://www.gitlab.com/installation/).
+The recommended way to install GitLab is using the provided [Omnibus packages](https://about.gitlab.com/downloads/). Compared to an installation from source, this is faster and less error prone. Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager.
-### New versions
+There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information.
-Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://www.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
+You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password.
-### Upgrading
+## Third-party applications
-For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update).
+There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages.
-## Run in production mode
+## GitLab release cycle
-The Installation guide contains instructions on how to download an init script and run it automatically on boot. You can also start the init script manually:
+Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
- sudo service gitlab start
+## Upgrading
-or by directly calling the script:
-
- sudo /etc/init.d/gitlab start
-
-Please login with `root` / `5iveL!fe`
+For updating the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For installations from source there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update) detailing all necessary commands to migrate to the next version.
## Install a development environment
-We recommend setting up your development environment with [the cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md#installation). If you do not use the cookbook you might need to copy the example development unicorn configuration file
+To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
+If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
+One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
cp config/unicorn.rb.example.development config/unicorn.rb
-## Run in development mode
-
-Start it with [Foreman](https://github.com/ddollar/foreman)
-
- bundle exec foreman start -p 3000
-
-or start each component separately:
-
- bundle exec rails s
- bin/background_jobs start
-
-And surf to [localhost:3000](http://localhost:3000/) and login with `root` / `5iveL!fe`.
-
-## Run the tests
-
-- Run all tests:
-
- bundle exec rake test
-
-- [RSpec](http://rspec.info/) unit and functional tests.
-
- All RSpec tests: `bundle exec rake spec`
-
- Single RSpec file: `bundle exec rspec spec/controllers/commit_controller_spec.rb`
-
-- [Spinach](https://github.com/codegram/spinach) integration tests.
-
- All Spinach tests: `bundle exec rake spinach`
-
- Single Spinach test: `bundle exec spinach features/project/issues/milestones.feature`
+Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
## Documentation
@@ -126,7 +92,7 @@ All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/
## Getting help
-Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help.
+Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on our website for the many options to get help.
## Is it any good?
@@ -135,4 +101,4 @@ Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on ou
## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
-[These people](https://twitter.com/gitlabhq/favorites) seem to like it.
+[These people](https://twitter.com/gitlab/favorites) seem to like it.
diff --git a/VERSION b/VERSION
index b26a34e470..9c51f30944 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-7.2.1
+7.10.0
\ No newline at end of file
diff --git a/app/assets/images/authbuttons/bitbucket_64.png b/app/assets/images/authbuttons/bitbucket_64.png
new file mode 100644
index 0000000000..4b90a57bc7
Binary files /dev/null and b/app/assets/images/authbuttons/bitbucket_64.png differ
diff --git a/app/assets/images/authbuttons/github_32.png b/app/assets/images/authbuttons/github_32.png
deleted file mode 100644
index c56eef05eb..0000000000
Binary files a/app/assets/images/authbuttons/github_32.png and /dev/null differ
diff --git a/app/assets/images/authbuttons/github_64.png b/app/assets/images/authbuttons/github_64.png
index 39de55bc79..dc7c03d100 100644
Binary files a/app/assets/images/authbuttons/github_64.png and b/app/assets/images/authbuttons/github_64.png differ
diff --git a/app/assets/images/authbuttons/gitlab_64.png b/app/assets/images/authbuttons/gitlab_64.png
new file mode 100644
index 0000000000..31281a1944
Binary files /dev/null and b/app/assets/images/authbuttons/gitlab_64.png differ
diff --git a/app/assets/images/authbuttons/google_32.png b/app/assets/images/authbuttons/google_32.png
deleted file mode 100644
index 6225cc9c2d..0000000000
Binary files a/app/assets/images/authbuttons/google_32.png and /dev/null differ
diff --git a/app/assets/images/authbuttons/google_64.png b/app/assets/images/authbuttons/google_64.png
index 4d608f7100..94a0e089c6 100644
Binary files a/app/assets/images/authbuttons/google_64.png and b/app/assets/images/authbuttons/google_64.png differ
diff --git a/app/assets/images/authbuttons/twitter_32.png b/app/assets/images/authbuttons/twitter_32.png
deleted file mode 100644
index 696eb02484..0000000000
Binary files a/app/assets/images/authbuttons/twitter_32.png and /dev/null differ
diff --git a/app/assets/images/authbuttons/twitter_64.png b/app/assets/images/authbuttons/twitter_64.png
index 2893274766..5c9f14cb07 100644
Binary files a/app/assets/images/authbuttons/twitter_64.png and b/app/assets/images/authbuttons/twitter_64.png differ
diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png
index 9ecdaf4e2d..639271c6fa 100644
Binary files a/app/assets/images/bg-header.png and b/app/assets/images/bg-header.png differ
diff --git a/app/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png
index d9066ad7d7..e5fe659ba6 100644
Binary files a/app/assets/images/bg_fallback.png and b/app/assets/images/bg_fallback.png differ
diff --git a/app/assets/images/brand_logo.png b/app/assets/images/brand_logo.png
index 09b1689ca4..9c564bb614 100644
Binary files a/app/assets/images/brand_logo.png and b/app/assets/images/brand_logo.png differ
diff --git a/app/assets/images/chosen-sprite.png b/app/assets/images/chosen-sprite.png
index d08e4b7e62..3d936b07d4 100644
Binary files a/app/assets/images/chosen-sprite.png and b/app/assets/images/chosen-sprite.png differ
diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png
index 6dac6cd8ca..2ef58e5254 100644
Binary files a/app/assets/images/dark-scheme-preview.png and b/app/assets/images/dark-scheme-preview.png differ
diff --git a/app/assets/images/diff_note_add.png b/app/assets/images/diff_note_add.png
index 8ec15b701f..0084422e33 100644
Binary files a/app/assets/images/diff_note_add.png and b/app/assets/images/diff_note_add.png differ
diff --git a/app/assets/images/gitorious-logo-black.png b/app/assets/images/gitorious-logo-black.png
new file mode 100644
index 0000000000..78f17a9af7
Binary files /dev/null and b/app/assets/images/gitorious-logo-black.png differ
diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png
new file mode 100644
index 0000000000..4962cffba3
Binary files /dev/null and b/app/assets/images/gitorious-logo-blue.png differ
diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png
index 32ade0fe9a..60021d5ac4 100644
Binary files a/app/assets/images/icon-link.png and b/app/assets/images/icon-link.png differ
diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png
index 084b89e3a7..3c1c146541 100644
Binary files a/app/assets/images/icon-search.png and b/app/assets/images/icon-search.png differ
diff --git a/app/assets/images/icon_sprite.png b/app/assets/images/icon_sprite.png
index 9ad65fc443..2e7a502339 100644
Binary files a/app/assets/images/icon_sprite.png and b/app/assets/images/icon_sprite.png differ
diff --git a/app/assets/images/images.png b/app/assets/images/images.png
index da91f6b1f4..ad146246ca 100644
Binary files a/app/assets/images/images.png and b/app/assets/images/images.png differ
diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png
deleted file mode 100644
index 4a96572d57..0000000000
Binary files a/app/assets/images/logo-black.png and /dev/null differ
diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png
index bc2ef601a5..917bcfcb7e 100644
Binary files a/app/assets/images/logo-white.png and b/app/assets/images/logo-white.png differ
diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png
index 3aeed886a0..fbb339c6a9 100644
Binary files a/app/assets/images/monokai-scheme-preview.png and b/app/assets/images/monokai-scheme-preview.png differ
diff --git a/app/assets/images/move.png b/app/assets/images/move.png
index 9d2d55ddf0..6a0567f8f2 100644
Binary files a/app/assets/images/move.png and b/app/assets/images/move.png differ
diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png
index dac3ab1bb8..8287acbce1 100644
Binary files a/app/assets/images/no_avatar.png and b/app/assets/images/no_avatar.png differ
diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png
index a97d451598..bfb31bb218 100644
Binary files a/app/assets/images/no_group_avatar.png and b/app/assets/images/no_group_avatar.png differ
diff --git a/app/assets/images/slider_handles.png b/app/assets/images/slider_handles.png
index a6d477033f..884378ec96 100644
Binary files a/app/assets/images/slider_handles.png and b/app/assets/images/slider_handles.png differ
diff --git a/app/assets/images/solarized-dark-scheme-preview.png b/app/assets/images/solarized-dark-scheme-preview.png
index ae092ab521..7ed7336896 100644
Binary files a/app/assets/images/solarized-dark-scheme-preview.png and b/app/assets/images/solarized-dark-scheme-preview.png differ
diff --git a/app/assets/images/solarized-light-scheme-preview.png b/app/assets/images/solarized-light-scheme-preview.png
new file mode 100644
index 0000000000..c50db75449
Binary files /dev/null and b/app/assets/images/solarized-light-scheme-preview.png differ
diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png
index 6b8bde41bc..c6b6c8d952 100644
Binary files a/app/assets/images/switch_icon.png and b/app/assets/images/switch_icon.png differ
diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif
index 5f6ed04a43..1a1c9c15ec 100644
Binary files a/app/assets/images/trans_bg.gif and b/app/assets/images/trans_bg.gif differ
diff --git a/app/assets/images/white-scheme-preview.png b/app/assets/images/white-scheme-preview.png
index d1866e0015..fc4c40b922 100644
Binary files a/app/assets/images/white-scheme-preview.png and b/app/assets/images/white-scheme-preview.png differ
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
index fdefbfb92b..777c62dc1b 100644
--- a/app/assets/javascripts/activities.js.coffee
+++ b/app/assets/javascripts/activities.js.coffee
@@ -1,4 +1,4 @@
-class Activities
+class @Activities
constructor: ->
Pager.init 20, true
$(".event_filter_link").bind "click", (event) =>
@@ -12,7 +12,7 @@ class Activities
toggleFilter: (sender) ->
- sender.parent().toggleClass "inactive"
+ sender.parent().toggleClass "active"
event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0]
if event_filters
@@ -27,5 +27,3 @@ class Activities
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' }
-
-@Activities = Activities
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
index 6634bb6cc3..bcb2e6df7c 100644
--- a/app/assets/javascripts/admin.js.coffee
+++ b/app/assets/javascripts/admin.js.coffee
@@ -1,4 +1,4 @@
-class Admin
+class @Admin
constructor: ->
$('input#user_force_random_password').on 'change', (elem) ->
elems = $('#user_password, #user_password_confirmation')
@@ -46,10 +46,8 @@ class Admin
modal.hide()
$('.change-owner-link').show()
- $('li.users_project').bind 'ajax:success', ->
+ $('li.project_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
- $('li.users_group').bind 'ajax:success', ->
+ $('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
-
-@Admin = Admin
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index fafa5cdfaa..9e5d594c86 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -1,44 +1,24 @@
@Api =
- users_path: "/api/:version/users.json"
- user_path: "/api/:version/users/:id.json"
- notes_path: "/api/:version/projects/:id/notes.json"
+ groups_path: "/api/:version/groups.json"
+ group_path: "/api/:version/groups/:id.json"
namespaces_path: "/api/:version/namespaces.json"
- project_users_path: "/api/:version/projects/:id/users.json"
- # Get 20 (depends on api) recent notes
- # and sort the ascending from oldest to newest
- notes: (project_id, callback) ->
- url = Api.buildUrl(Api.notes_path)
- url = url.replace(':id', project_id)
-
- $.ajax(
- url: url,
- data:
- private_token: gon.api_token
- gfm: true
- recent: true
- dataType: "json"
- ).done (notes) ->
- notes.sort (a, b) ->
- return a.id - b.id
- callback(notes)
-
- user: (user_id, callback) ->
- url = Api.buildUrl(Api.user_path)
- url = url.replace(':id', user_id)
+ group: (group_id, callback) ->
+ url = Api.buildUrl(Api.group_path)
+ url = url.replace(':id', group_id)
$.ajax(
url: url
data:
private_token: gon.api_token
dataType: "json"
- ).done (user) ->
- callback(user)
+ ).done (group) ->
+ callback(group)
- # Return users list. Filtered by query
- # Only active users retrieved
- users: (query, callback) ->
- url = Api.buildUrl(Api.users_path)
+ # Return groups list. Filtered by query
+ # Only active groups retrieved
+ groups: (query, skip_ldap, callback) ->
+ url = Api.buildUrl(Api.groups_path)
$.ajax(
url: url
@@ -46,27 +26,9 @@
private_token: gon.api_token
search: query
per_page: 20
- active: true
dataType: "json"
- ).done (users) ->
- callback(users)
-
- # Return project users list. Filtered by query
- # Only active users retrieved
- projectUsers: (project_id, query, callback) ->
- url = Api.buildUrl(Api.project_users_path)
- url = url.replace(':id', project_id)
-
- $.ajax(
- url: url
- data:
- private_token: gon.api_token
- search: query
- per_page: 20
- active: true
- dataType: "json"
- ).done (users) ->
- callback(users)
+ ).done (groups) ->
+ callback(groups)
# Return namespaces list. Filtered by query
namespaces: (query, callback) ->
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 1960479321..fda142293b 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -15,22 +15,32 @@
#= require jquery.atwho
#= require jquery.scrollTo
#= require jquery.blockUI
-#= require turbolinks
#= require jquery.turbolinks
+#= require jquery.sticky-kit.min
+#= require turbolinks
+#= require autosave
#= require bootstrap
#= require select2
#= require raphael
#= require g.raphael-min
#= require g.bar-min
+#= require chart-lib.min
#= require branch-graph
-#= require highlight.pack
#= require ace/ace
+#= require ace/ext-searchbox
#= require d3
#= require underscore
#= require nprogress
#= require nprogress-turbolinks
#= require dropzone
-#= require semantic-ui/sidebar
+#= require mousetrap
+#= require mousetrap/pause
+#= require shortcuts
+#= require shortcuts_navigation
+#= require shortcuts_dashboard_navigation
+#= require shortcuts_issueable
+#= require shortcuts_network
+#= require cal-heatmap
#= require_tree .
window.slugify = (text) ->
@@ -41,12 +51,6 @@ window.ajaxGet = (url) ->
window.showAndHide = (selector) ->
-window.errorMessage = (message) ->
- ehtml = $("
")
- ehtml.addClass("error_message")
- ehtml.html(message)
- ehtml
-
window.split = (val) ->
return val.split( /,\s*/ )
@@ -54,7 +58,7 @@ window.extractLast = (term) ->
return split( term ).pop()
window.rstrip = (val) ->
- return val.replace(/\s+$/, '')
+ return if val then val.replace(/\s+$/, '') else val
# Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
@@ -72,24 +76,18 @@ window.disableButtonIfEmptyField = (field_selector, button_selector) ->
# Disable button if any input field with given selector is empty
window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
closest_submit = form.find(button_selector)
- empty = false
- form.find('input').filter(form_selector).each ->
- empty = true if rstrip($(this).val()) is ""
-
- if empty
- closest_submit.disable()
- else
- closest_submit.enable()
-
- form.keyup ->
- empty = false
+ updateButtons = ->
+ filled = true
form.find('input').filter(form_selector).each ->
- empty = true if rstrip($(this).val()) is ""
+ filled = rstrip($(this).val()) != "" || !$(this).attr('required')
- if empty
- closest_submit.disable()
- else
+ if filled
closest_submit.enable()
+ else
+ closest_submit.disable()
+
+ updateButtons()
+ form.keyup(updateButtons)
window.sanitize = (str) ->
return str.replace(/<(?:.|\n)*?>/gm, '')
@@ -105,8 +103,17 @@ window.unbindEvents = ->
$(document).unbind('scroll')
$(document).off('scroll')
+window.shiftWindow = ->
+ scrollBy 0, -50
+
document.addEventListener("page:fetch", unbindEvents)
+# Scroll the window to avoid the topnav bar
+# https://github.com/twitter/bootstrap/issues/1768
+if location.hash
+ setTimeout shiftWindow, 1
+window.addEventListener "hashchange", shiftWindow
+
$ ->
# Click a .one_click_select field, select the contents
$(".one_click_select").on 'click', -> $(@).select()
@@ -117,6 +124,13 @@ $ ->
# Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
+ # Close select2 on escape
+ $('.js-select2').bind 'select2-close', ->
+ setTimeout ( ->
+ $('.select2-container-active').removeClass('select2-container-active')
+ $(':focus').blur()
+ ), 1
+
# Initialize tooltips
$('.has_tooltip').tooltip()
@@ -134,7 +148,6 @@ $ ->
if (flash = $(".flash-container")).length > 0
flash.click -> $(@).fadeOut()
flash.show()
- setTimeout (-> flash.fadeOut()), 5000
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
@@ -149,20 +162,6 @@ $ ->
# Show/Hide the profile menu when hovering the account box
$('.account-box').hover -> $(@).toggleClass('hover')
- # Focus search field by pressing 's' key
- $(document).keypress (e) ->
- # Don't do anything if typing in an input
- return if $(e.target).is(":input")
-
- switch e.which
- when 115
- $("#search").focus()
- e.preventDefault()
- when 63
- new Shortcuts()
- e.preventDefault()
-
-
# Commit show suppressed diff
$(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show()
@@ -170,12 +169,19 @@ $ ->
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
- $(@).find('i').
- toggleClass('icon-chevron-down').
- toggleClass('icon-chevron-up')
+ $(@).toggleClass('active')
$(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault()
+ $(document).on "click", '.js-confirm-danger', (e) ->
+ e.preventDefault()
+ btn = $(e.target)
+ text = btn.data("confirm-danger-message")
+ form = btn.closest("form")
+ new ConfirmDangerModal(form, text)
+
+ new Aside()
+
(($) ->
# Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: ->
diff --git a/app/assets/javascripts/aside.js.coffee b/app/assets/javascripts/aside.js.coffee
new file mode 100644
index 0000000000..8547310194
--- /dev/null
+++ b/app/assets/javascripts/aside.js.coffee
@@ -0,0 +1,17 @@
+class @Aside
+ constructor: ->
+ $(document).off "click", "a.show-aside"
+ $(document).on "click", 'a.show-aside', (e) ->
+ e.preventDefault()
+ btn = $(e.currentTarget)
+ icon = btn.find('i')
+ console.log('1')
+
+ if icon.hasClass('fa-angle-left')
+ btn.parent().find('section').hide()
+ btn.parent().find('aside').fadeIn()
+ icon.removeClass('fa-angle-left').addClass('fa-angle-right')
+ else
+ btn.parent().find('aside').hide()
+ btn.parent().find('section').fadeIn()
+ icon.removeClass('fa-angle-right').addClass('fa-angle-left')
diff --git a/app/assets/javascripts/autosave.js.coffee b/app/assets/javascripts/autosave.js.coffee
new file mode 100644
index 0000000000..5d3fe81da7
--- /dev/null
+++ b/app/assets/javascripts/autosave.js.coffee
@@ -0,0 +1,39 @@
+class @Autosave
+ constructor: (field, key) ->
+ @field = field
+
+ key = key.join("/") if key.join?
+ @key = "autosave/#{key}"
+
+ @field.data "autosave", this
+
+ @restore()
+
+ @field.on "input", => @save()
+
+ restore: ->
+ return unless window.localStorage?
+
+ try
+ text = window.localStorage.getItem @key
+ catch
+ return
+
+ @field.val text if text?.length > 0
+ @field.trigger "input"
+
+ save: ->
+ return unless window.localStorage?
+
+ text = @field.val()
+ if text?.length > 0
+ try
+ window.localStorage.setItem @key, text
+ else
+ @reset()
+
+ reset: ->
+ return unless window.localStorage?
+
+ try
+ window.localStorage.removeItem @key
diff --git a/app/assets/javascripts/behaviors/taskable.js.coffee b/app/assets/javascripts/behaviors/taskable.js.coffee
new file mode 100644
index 0000000000..ddce71c188
--- /dev/null
+++ b/app/assets/javascripts/behaviors/taskable.js.coffee
@@ -0,0 +1,21 @@
+window.updateTaskState = (taskableType) ->
+ objType = taskableType.data
+ isChecked = $(this).prop("checked")
+ if $(this).is(":checked")
+ stateEvent = "task_check"
+ else
+ stateEvent = "task_uncheck"
+
+ taskableUrl = $("form.edit-" + objType).first().attr("action")
+ taskableNum = taskableUrl.match(/\d+$/)
+ taskNum = 0
+ $("li.task-list-item input:checkbox").each( (index, e) =>
+ if e == this
+ taskNum = index + 1
+ )
+
+ $.ajax
+ type: "PATCH"
+ url: taskableUrl
+ data: objType + "[state_event]=" + stateEvent +
+ "&" + objType + "[task_num]=" + taskNum
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee
index 1b2ed9efc2..177b691827 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.coffee
+++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee
@@ -8,7 +8,7 @@ $ ->
#
$("body").on "click", ".js-toggle-button", (e) ->
$(@).find('i').
- toggleClass('icon-chevron-down').
- toggleClass('icon-chevron-up')
+ toggleClass('fa fa-chevron-down').
+ toggleClass('fa fa-chevron-up')
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
e.preventDefault()
diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob/blob.js.coffee
similarity index 96%
rename from app/assets/javascripts/blob.js.coffee
rename to app/assets/javascripts/blob/blob.js.coffee
index 9db919e5a6..37a175fdbc 100644
--- a/app/assets/javascripts/blob.js.coffee
+++ b/app/assets/javascripts/blob/blob.js.coffee
@@ -1,4 +1,4 @@
-class BlobView
+class @BlobView
constructor: ->
# handle multi-line select
handleMultiSelect = (e) ->
@@ -26,7 +26,7 @@ class BlobView
unless isNaN first_line
$("#tree-content-holder .highlight .line").removeClass("hll")
$("#LC#{line}").addClass("hll") for line in [first_line..last_line]
- $.scrollTo("#L#{first_line}") unless e?
+ $.scrollTo("#L#{first_line}", offset: -50) unless e?
# parse selected lines from hash
# always return first and last line (initialized to NaN)
@@ -71,6 +71,3 @@ class BlobView
# Highlight the correct lines when the hash part of the URL changes
$(window).on("hashchange", highlightBlobLines)
-
-
-@BlobView = BlobView
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
new file mode 100644
index 0000000000..2e91a06daa
--- /dev/null
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -0,0 +1,44 @@
+class @EditBlob
+ constructor: (assets_path, mode)->
+ ace.config.set "modePath", assets_path + '/ace'
+ ace.config.loadModule "ace/ext/searchbox"
+ if mode
+ ace_mode = mode
+ editor = ace.edit("editor")
+ editor.focus()
+ @editor = editor
+
+ if ace_mode
+ editor.getSession().setMode "ace/mode/" + ace_mode
+
+ disableButtonIfEmptyField "#commit_message", ".js-commit-button"
+ $(".js-commit-button").click ->
+ $("#file-content").val editor.getValue()
+ $(".file-editor form").submit()
+ return false
+
+ editModePanes = $(".js-edit-mode-pane")
+ editModeLinks = $(".js-edit-mode a")
+ editModeLinks.click (event) ->
+ event.preventDefault()
+ currentLink = $(this)
+ paneId = currentLink.attr("href")
+ currentPane = editModePanes.filter(paneId)
+ editModeLinks.parent().removeClass "active hover"
+ currentLink.parent().addClass "active hover"
+ editModePanes.hide()
+ if paneId is "#preview"
+ currentPane.fadeIn 200
+ $.post currentLink.data("preview-url"),
+ content: editor.getValue()
+ , (response) ->
+ currentPane.empty().append response
+ return
+
+ else
+ currentPane.fadeIn 200
+ editor.focus()
+ return
+
+ editor: ->
+ return @editor
diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee
new file mode 100644
index 0000000000..ab8f98715e
--- /dev/null
+++ b/app/assets/javascripts/blob/new_blob.js.coffee
@@ -0,0 +1,21 @@
+class @NewBlob
+ constructor: (assets_path, mode)->
+ ace.config.set "modePath", assets_path + '/ace'
+ ace.config.loadModule "ace/ext/searchbox"
+ if mode
+ ace_mode = mode
+ editor = ace.edit("editor")
+ editor.focus()
+ @editor = editor
+
+ if ace_mode
+ editor.getSession().setMode "ace/mode/" + ace_mode
+
+ disableButtonIfEmptyField "#commit_message", ".js-commit-button"
+ $(".js-commit-button").click ->
+ $("#file-content").val editor.getValue()
+ $(".file-editor form").submit()
+ return false
+
+ editor: ->
+ return @editor
diff --git a/app/assets/javascripts/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee
index f6d57bd55b..010a2b0e42 100644
--- a/app/assets/javascripts/branch-graph.js.coffee
+++ b/app/assets/javascripts/branch-graph.js.coffee
@@ -1,4 +1,4 @@
-class BranchGraph
+class @BranchGraph
constructor: (@element, @options) ->
@preparedCommits = {}
@mtime = 0
@@ -90,11 +90,15 @@ class BranchGraph
renderPartialGraph: ->
start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
- start = 0 if start < 0
+ if start < 0
+ isGraphEdge = true
+ start = 0
end = start + 40
- end = @commits.length if @commits.length < end
+ if @commits.length < end
+ isGraphEdge = true
+ end = @commits.length
- if @prev_start == -1 or Math.abs(@prev_start - start) > 10
+ if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge
i = start
@prev_start = start
@@ -120,23 +124,32 @@ class BranchGraph
@top.toFront()
bindEvents: ->
- drag = {}
element = @element
$(element).scroll (event) =>
@renderPartialGraph()
- $(window).on
- keydown: (event) =>
- # left
- element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37
- # top
- element.scrollTop element.scrollTop() - 50 if event.keyCode is 38
- # right
- element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39
- # bottom
- element.scrollTop element.scrollTop() + 50 if event.keyCode is 40
- @renderPartialGraph()
+ scrollDown: =>
+ @element.scrollTop @element.scrollTop() + 50
+ @renderPartialGraph()
+
+ scrollUp: =>
+ @element.scrollTop @element.scrollTop() - 50
+ @renderPartialGraph()
+
+ scrollLeft: =>
+ @element.scrollLeft @element.scrollLeft() - 50
+ @renderPartialGraph()
+
+ scrollRight: =>
+ @element.scrollLeft @element.scrollLeft() + 50
+ @renderPartialGraph()
+
+ scrollBottom: =>
+ @element.scrollTop @element.find('svg').height()
+
+ scrollTop: =>
+ @element.scrollTop 0
appendLabel: (x, y, commit) ->
return unless commit.refs
@@ -325,5 +338,3 @@ Raphael::textWrap = (t, width) ->
b = t.getBBox()
h = Math.abs(b.y2) - Math.abs(b.y) + 1
t.attr y: b.y + h
-
-@BranchGraph = BranchGraph
diff --git a/app/assets/javascripts/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee
new file mode 100644
index 0000000000..44d75bd694
--- /dev/null
+++ b/app/assets/javascripts/calendar.js.coffee
@@ -0,0 +1,38 @@
+class @Calendar
+ options =
+ month: "short"
+ day: "numeric"
+ year: "numeric"
+
+ constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
+ cal = new CalHeatMap()
+ cal.init
+ itemName: ["contribution"]
+ data: timestamps
+ start: new Date(starting_year, starting_month)
+ domainLabelFormat: "%b"
+ id: "cal-heatmap"
+ domain: "month"
+ subDomain: "day"
+ range: 12
+ tooltip: true
+ label:
+ position: "top"
+ legend: [
+ 0
+ 10
+ 20
+ 30
+ ]
+ legendCellPadding: 3
+ onClick: (date, count) ->
+ formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
+ $.ajax
+ url: calendar_activities_path
+ data:
+ date: formated_date
+ cache: false
+ dataType: "html"
+ success: (data) ->
+ $(".user-calendar-activities").html data
+
diff --git a/app/assets/javascripts/chart.js.coffee b/app/assets/javascripts/chart.js.coffee
deleted file mode 100644
index 989f48e5e7..0000000000
--- a/app/assets/javascripts/chart.js.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-@Chart =
- labels: []
- values: []
-
- init: (labels, values, title) ->
- r = Raphael('activity-chart')
-
- fin = ->
- @flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this)
-
- fout = ->
- @flag.animate
- opacity: 0, 300, -> @remove()
-
- r.text(160, 10, title).attr font: "13px sans-serif"
- r.barchart(
- 10, 20, 560, 200,
- [values],
- {colors:["#456"]}
- ).label(labels, true)
- .hover(fin, fout)
diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee
index 5f53439ca4..0566e23919 100644
--- a/app/assets/javascripts/commit.js.coffee
+++ b/app/assets/javascripts/commit.js.coffee
@@ -1,6 +1,4 @@
-class Commit
+class @Commit
constructor: ->
$('.files .diff-file').each ->
new CommitFile(this)
-
-@Commit = Commit
diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee
index 4db9116a9d..83e793863b 100644
--- a/app/assets/javascripts/commit/file.js.coffee
+++ b/app/assets/javascripts/commit/file.js.coffee
@@ -1,7 +1,5 @@
-class CommitFile
+class @CommitFile
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
-
-@CommitFile = CommitFile
diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee
index 607b85eb45..9e5f49b1f6 100644
--- a/app/assets/javascripts/commit/image-file.js.coffee
+++ b/app/assets/javascripts/commit/image-file.js.coffee
@@ -1,4 +1,4 @@
-class ImageFile
+class @ImageFile
# Width where images must fits in, for 2-up this gets divided by 2
@availWidth = 900
@@ -124,5 +124,3 @@ class ImageFile
else
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
-
-@ImageFile = ImageFile
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index 784d7d20bb..c183e78e51 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -1,4 +1,4 @@
-class CommitsList
+class @CommitsList
@data =
ref: null
limit: 0
@@ -53,5 +53,3 @@ class CommitsList
@disable
callback: =>
this.getOld()
-
-this.CommitsList = CommitsList
diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee
new file mode 100644
index 0000000000..bb99edbd09
--- /dev/null
+++ b/app/assets/javascripts/confirm_danger_modal.js.coffee
@@ -0,0 +1,18 @@
+class @ConfirmDangerModal
+ constructor: (form, text) ->
+ @form = form
+ $('.js-confirm-text').text(text || '')
+ $('.js-confirm-danger-input').val('')
+ $('#modal-confirm-danger').modal('show')
+ project_path = $('.js-confirm-danger-match').text()
+ submit = $('.js-confirm-danger-submit')
+ submit.disable()
+
+ $('.js-confirm-danger-input').on 'input', ->
+ if rstrip($(@).val()) is project_path
+ submit.enable()
+ else
+ submit.disable()
+
+ $('.js-confirm-danger-submit').on 'click', =>
+ @form.submit()
diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee
index c4a0ccd9c2..00ee503ff1 100644
--- a/app/assets/javascripts/dashboard.js.coffee
+++ b/app/assets/javascripts/dashboard.js.coffee
@@ -1,33 +1,3 @@
-class Dashboard
+class @Dashboard
constructor: ->
- @initSidebarTab()
-
- $(".dash-filter").keyup ->
- terms = $(this).val()
- uiBox = $(this).parents('.panel').first()
- if terms == "" || terms == undefined
- uiBox.find(".dash-list li").show()
- else
- uiBox.find(".dash-list li").each (index) ->
- name = $(this).find(".filter-title").text()
-
- if name.toLowerCase().search(terms.toLowerCase()) == -1
- $(this).hide()
- else
- $(this).show()
-
-
-
- initSidebarTab: ->
- key = "dashboard_sidebar_filter"
-
- # store selection in cookie
- $('.dash-sidebar-tabs a').on 'click', (e) ->
- $.cookie(key, $(e.target).attr('id'))
-
- # show tab from cookie
- sidebar_filter = $.cookie(key)
- $("#" + sidebar_filter).tab('show') if sidebar_filter
-
-
-@Dashboard = Dashboard
+ new ProjectsList()
diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee
index dbe00c487d..069f91c30e 100644
--- a/app/assets/javascripts/diff.js.coffee
+++ b/app/assets/javascripts/diff.js.coffee
@@ -1,6 +1,7 @@
-class Diff
+class @Diff
UNFOLD_COUNT = 20
constructor: ->
+ $(document).off('click', '.js-unfold')
$(document).on('click', '.js-unfold', (event) =>
target = $(event.target)
unfoldBottom = target.hasClass('js-unfold-bottom')
@@ -41,6 +42,3 @@ class Diff
lines = line.children().slice(0, 2)
line_numbers = ($(l).attr('data-linenumber') for l in lines)
(parseInt(line_number) for line_number in line_numbers)
-
-
-@Diff = Diff
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index e5e62c87e4..330ebac6f7 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -4,7 +4,6 @@ $ ->
class Dispatcher
constructor: () ->
@initSearch()
- @initHighlight()
@initPageScripts()
initPageScripts: ->
@@ -15,50 +14,137 @@ class Dispatcher
return false
path = page.split(':')
+ shortcut_handler = null
switch page
when 'projects:issues:index'
Issues.init()
+ shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
+ shortcut_handler = new ShortcutsIssueable()
+ new ZenMode()
when 'projects:milestones:show'
new Milestone()
- when 'projects:issues:new'
+ when 'projects:milestones:new', 'projects:milestones:edit'
+ new ZenMode()
+ when 'projects:compare:show'
+ new Diff()
+ when 'projects:issues:new','projects:issues:edit'
GitLab.GfmAutoComplete.setup()
- when 'projects:merge_requests:new'
+ shortcut_handler = new ShortcutsNavigation()
+ new ZenMode()
+ new DropzoneInput($('.issue-form'))
+ if page == 'projects:issues:new'
+ new IssuableForm($('.issue-form'))
+ when 'projects:merge_requests:new', 'projects:merge_requests:edit'
GitLab.GfmAutoComplete.setup()
new Diff()
+ shortcut_handler = new ShortcutsNavigation()
+ new ZenMode()
+ new DropzoneInput($('.merge-request-form'))
+ if page == 'projects:merge_requests:new'
+ new IssuableForm($('.merge-request-form'))
when 'projects:merge_requests:show'
new Diff()
+ shortcut_handler = new ShortcutsIssueable()
+ new ZenMode()
when "projects:merge_requests:diffs"
new Diff()
+ new ZenMode()
+ when 'projects:merge_requests:index'
+ shortcut_handler = new ShortcutsNavigation()
+ MergeRequests.init()
when 'dashboard:show'
new Dashboard()
new Activities()
+ when 'dashboard:projects:starred'
+ new Activities()
+ new ProjectsList()
when 'projects:commit:show'
new Commit()
new Diff()
- when 'groups:show', 'projects:show'
+ new ZenMode()
+ shortcut_handler = new ShortcutsNavigation()
+ when 'projects:commits:show'
+ shortcut_handler = new ShortcutsNavigation()
+ when 'projects:show'
new Activities()
- when 'projects:new', 'projects:edit'
- new Project()
- when 'projects:teams:members:index'
- new TeamMembers()
- when 'groups:members'
+ shortcut_handler = new ShortcutsNavigation()
+ when 'groups:show'
+ new Activities()
+ shortcut_handler = new ShortcutsNavigation()
+ new ProjectsList()
+ when 'groups:group_members:index'
new GroupMembers()
+ new UsersSelect()
+ when 'projects:project_members:index'
+ new ProjectMembers()
+ new UsersSelect()
+ when 'groups:new', 'groups:edit', 'admin:groups:edit'
+ new GroupAvatar()
when 'projects:tree:show'
new TreeView()
+ shortcut_handler = new ShortcutsNavigation()
when 'projects:blob:show'
new BlobView()
+ shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
+ when 'projects:network:show'
+ # Ensure we don't create a particular shortcut handler here. This is
+ # already created, where the network graph is created.
+ shortcut_handler = true
+ when 'projects:forks:new'
+ new ProjectFork()
+ when 'users:show'
+ new User()
+ new Activities()
+ when 'admin:users:show'
+ new ProjectsList()
switch path.first()
- when 'admin' then new Admin()
+ when 'admin'
+ new Admin()
+ switch path[1]
+ when 'groups'
+ new UsersSelect()
+ when 'projects'
+ new NamespaceSelect()
+ when 'dashboard'
+ shortcut_handler = new ShortcutsDashboardNavigation()
+ when 'profiles'
+ new Profile()
when 'projects'
- new Wikis() if path[1] == 'wikis'
+ new Project()
+ new ProjectAvatar()
+ switch path[1]
+ when 'compare'
+ shortcut_handler = new ShortcutsNavigation()
+ when 'edit'
+ shortcut_handler = new ShortcutsNavigation()
+ new ProjectNew()
+ when 'new'
+ new ProjectNew()
+ when 'show'
+ new ProjectShow()
+ when 'issues', 'merge_requests'
+ new UsersSelect()
+ when 'wikis'
+ new Wikis()
+ shortcut_handler = new ShortcutsNavigation()
+ new ZenMode()
+ new DropzoneInput($('.wiki-form'))
+ when 'snippets', 'labels', 'graphs'
+ shortcut_handler = new ShortcutsNavigation()
+ when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
+ shortcut_handler = new ShortcutsNavigation()
+ # If we haven't installed a custom shortcut handler, install the default one
+ if not shortcut_handler
+ new Shortcuts()
+
initSearch: ->
opts = $('.search-autocomplete-opts')
path = opts.data('autocomplete-path')
@@ -66,10 +152,3 @@ class Dispatcher
project_ref = opts.data('autocomplete-project-ref')
new SearchAutocomplete(path, project_id, project_ref)
-
- initHighlight: ->
- $('.highlight pre code').each (i, e) ->
- $(e).html($.map($(e).html().split("\n"), (line, i) ->
- "" + line + ""
- ).join("\n"))
- hljs.highlightBlock(e)
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
new file mode 100644
index 0000000000..fca2a290e2
--- /dev/null
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -0,0 +1,243 @@
+class @DropzoneInput
+ constructor: (form) ->
+ Dropzone.autoDiscover = false
+ alertClass = "alert alert-danger alert-dismissable div-dropzone-alert"
+ alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\""
+ divHover = "
Access denied! Please verify you can add deploy keys to this repository.
")
+- else
+ :plain
+ job = $("tr#repo_#{@repo_id}")
+ job.attr("id", "project_#{@project.id}")
+ target_field = job.find(".import-target")
+ target_field.empty()
+ target_field.append('#{link_to @project.path_with_namespace, [@project.namespace.becomes(Namespace), @project]}')
+ $("table.import-jobs tbody").prepend(job)
+ job.addClass("active").find(".import-actions").html(" started")
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
new file mode 100644
index 0000000000..4e49bbbc7f
--- /dev/null
+++ b/app/views/import/bitbucket/status.html.haml
@@ -0,0 +1,45 @@
+%h3.page-title
+ %i.fa.fa-bitbucket
+ Import projects from Bitbucket
+
+%p.light
+ Select projects you want to import.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From Bitbucket
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
+ %td
+ = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
+ %td.import-target
+ = "#{repo["owner"]}/#{repo["slug"]}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}")
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
new file mode 100644
index 0000000000..f0bc3e6b1a
--- /dev/null
+++ b/app/views/import/github/status.html.haml
@@ -0,0 +1,45 @@
+%h3.page-title
+ %i.fa.fa-github
+ Import projects from GitHub
+
+%p.light
+ Select projects you want to import.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From GitHub
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
+ %td.import-target
+ = repo.full_name
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}")
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
new file mode 100644
index 0000000000..33b0a21acf
--- /dev/null
+++ b/app/views/import/gitlab/status.html.haml
@@ -0,0 +1,45 @@
+%h3.page-title
+ %i.fa.fa-heart
+ Import projects from GitLab.com
+
+%p.light
+ Select projects you want to import.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From GitLab.com
+ %th To this GitLab instance
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo["id"]}"}
+ %td
+ = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
+ %td.import-target
+ = repo["path_with_namespace"]
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}")
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
new file mode 100644
index 0000000000..78c5e957be
--- /dev/null
+++ b/app/views/import/gitorious/status.html.haml
@@ -0,0 +1,45 @@
+%h3.page-title
+ %i.icon-gitorious.icon-gitorious-big
+ Import projects from Gitorious.org
+
+%p.light
+ Select projects you want to import.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From Gitorious.org
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
+ %td.import-target
+ = repo.full_name
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}")
diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml
new file mode 100644
index 0000000000..ce78fec205
--- /dev/null
+++ b/app/views/import/google_code/new.html.haml
@@ -0,0 +1,60 @@
+%h3.page-title
+ %i.fa.fa-google
+ Import projects from Google Code
+%hr
+
+= form_tag callback_import_google_code_path, class: 'form-horizontal', multipart: true do
+ %p
+ Follow the steps below to export your Google Code project data.
+ In the next step, you'll be able to select the projects you want to import.
+ %ol
+ %li
+ %p
+ Go to
+ #{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: "_blank"}.
+ %li
+ %p
+ Make sure you're logged into the account that owns the projects you'd like to import.
+ %li
+ %p
+ Click the Select none button on the right, since we only need "Google Code Project Hosting".
+ %li
+ %p
+ Scroll down to Google Code Project Hosting and enable the switch on the right.
+ %li
+ %p
+ Choose Next at the bottom of the page.
+ %li
+ %p
+ Leave the "File type" and "Delivery method" options on their default values.
+ %li
+ %p
+ Choose Create archive and wait for archiving to complete.
+ %li
+ %p
+ Click the Download button and wait for downloading to complete.
+ %li
+ %p
+ Find the downloaded ZIP file and decompress it.
+ %li
+ %p
+ Find the newly extracted Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json file.
+ %li
+ %p
+ Upload GoogleCodeProjectHosting.json here:
+ %p
+ %input{type: "file", name: "dump_file", id: "dump_file"}
+ %li
+ %p
+ Do you want to customize how Google Code email addresses and usernames are imported into GitLab?
+ %p
+ = label_tag :create_user_map_0 do
+ = radio_button_tag :create_user_map, 0, true
+ No, directly import the existing email addresses and usernames.
+ %p
+ = label_tag :create_user_map_1 do
+ = radio_button_tag :create_user_map, 1, false
+ Yes, let me map Google Code users to full names or GitLab users.
+ %li
+ %p
+ = submit_tag 'Continue to the next step', class: "btn btn-create"
diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml
new file mode 100644
index 0000000000..9c6824ecad
--- /dev/null
+++ b/app/views/import/google_code/new_user_map.html.haml
@@ -0,0 +1,42 @@
+%h3.page-title
+ %i.fa.fa-google
+ Import projects from Google Code
+%hr
+
+= form_tag create_user_map_import_google_code_path, class: 'form-horizontal' do
+ %p
+ Customize how Google Code email addresses and usernames are imported into GitLab.
+ In the next step, you'll be able to select the projects you want to import.
+ %p
+ The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of :. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.
+ %ul
+ %li
+ %strong Default: Directly import the Google Code email address or username
+ %p
+ "johnsmith@example.com": "johnsm...@example.com"
+ will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com.
+ The email address or username is masked to ensure the user's privacy.
+ %li
+ %strong Map a Google Code user to a GitLab user
+ %p
+ "johnsmith@example.com": "@johnsmith"
+ will add "By @johnsmith" to all issues and comments originally created by johnsmith@example.com,
+ and will set @johnsmith as the assignee on all issues originally assigned to johnsmith@example.com.
+ %li
+ %strong Map a Google Code user to a full name
+ %p
+ "johnsmith@example.com": "John Smith"
+ will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.
+ %li
+ %strong Map a Google Code user to a full email address
+ %p
+ "johnsmith@example.com": "johnsmith@example.com"
+ will add "By johnsmith@example.com" to all issues and comments originally created by johnsmith@example.com.
+ By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address.
+
+ .form-group
+ .col-sm-12
+ = text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15
+
+ .form-actions
+ = submit_tag 'Continue to the next step', class: "btn btn-create"
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
new file mode 100644
index 0000000000..2013b8c03c
--- /dev/null
+++ b/app/views/import/google_code/status.html.haml
@@ -0,0 +1,49 @@
+%h3.page-title
+ %i.fa.fa-google
+ Import projects from Google Code
+
+%p.light
+ Select projects you want to import.
+%p.light
+ Optionally, you can
+ = link_to "customize", new_user_map_import_google_code_path
+ how Google Code email addresses and usernames are imported into GitLab.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From Google Code
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
+ %td.import-target
+ = "#{current_user.username}/#{repo.name}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}")
diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml
new file mode 100644
index 0000000000..ab0ecffe4d
--- /dev/null
+++ b/app/views/invites/show.html.haml
@@ -0,0 +1,29 @@
+%h3.page-title Invitation
+
+%p
+ You have been invited
+ - if inviter = @member.created_by
+ by
+ = link_to inviter.name, user_url(inviter)
+ to join
+ - case @member.source
+ - when Project
+ - project = @member.source
+ project
+ %strong
+ = link_to project.name_with_namespace, namespace_project_url(project.namespace, project)
+ - when Group
+ - group = @member.source
+ group
+ %strong
+ = link_to group.name, group_url(group)
+ as #{@member.human_access}.
+
+- if @member.source.users.include?(current_user)
+ %p
+ However, you are already a member of this #{@member.source.is_a?(Group) ? "group" : "project"}.
+ Sign in using a different account to accept the invitation.
+- else
+ .actions
+ = link_to "Accept invitation", accept_invite_url(@token), method: :post, class: "btn btn-success"
+ = link_to "Decline", decline_invite_url(@token), method: :post, class: "btn btn-danger prepend-left-10"
diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml
index 5794e3de33..e7d477c225 100644
--- a/app/views/layouts/_broadcast.html.haml
+++ b/app/views/layouts/_broadcast.html.haml
@@ -1,4 +1,4 @@
- if broadcast_message.present?
.broadcast-message{ style: broadcast_styling(broadcast_message) }
- %i.icon-bullhorn
+ %i.fa.fa-bullhorn
= broadcast_message.message
diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml
new file mode 100644
index 0000000000..2ed51d87ca
--- /dev/null
+++ b/app/views/layouts/_collapse_button.html.haml
@@ -0,0 +1,4 @@
+- if nav_menu_collapsed?
+ = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
+- else
+ = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 0c27f679de..d12145651a 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -1,12 +1,5 @@
%head
%meta{charset: "utf-8"}
-
- -# Go repository retrieval support
- -# Need to be the fist thing in the head
- -# Since Go is using an XML parser to process HTML5
- -# https://github.com/gitlabhq/gitlabhq/pull/5958#issuecomment-45397555
- - if controller_name == 'projects' && action_name == 'show'
- %meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"}
%meta{content: "GitLab Community Edition", name: "description"}
%title
@@ -18,8 +11,8 @@
= javascript_include_tag "application"
= csrf_meta_tags
= include_gon
- :erb
-
+ %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
+ %meta{name: 'theme-color', content: '#474D57'}
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
@@ -30,6 +23,6 @@
= auto_discovery_link_tag :atom, projects_url(:atom, private_token: current_user.private_token), title: "Dashboard feed"
- if @project && !@project.new_record?
- if current_controller?(:tree, :commits)
- = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}")
+ = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}")
- if current_controller?(:issues)
- = auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
+ = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
index 7c727aca78..d58582c107 100644
--- a/app/views/layouts/_head_panel.html.haml
+++ b/app/views/layouts/_head_panel.html.haml
@@ -1,16 +1,14 @@
-%header.navbar.navbar-static-top.navbar-gitlab
+%header.navbar.navbar-fixed-top.navbar-gitlab
.navbar-inner
.container
%div.app_logo
- %span.separator
= link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do
- %h1 GITLAB
- %span.separator
+ = brand_header_logo
%h1.title= title
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
%span.sr-only Toggle navigation
- %i.icon-reorder
+ %i.fa.fa-bars
.navbar-collapse.collapse
%ul.nav.navbar-nav
@@ -18,31 +16,33 @@
= render "layouts/search"
%li.visible-sm.visible-xs
= link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do
- %i.icon-search
+ %i.fa.fa-search
%li
= link_to help_path, title: 'Help', class: 'has_bottom_tooltip',
'data-original-title' => 'Help' do
- %i.icon-question-sign
+ %i.fa.fa-question-circle
%li
= link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
- %i.icon-globe
+ %i.fa.fa-globe
%li
- = link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'My snippets' do
- %i.icon-paste
+ = link_to user_snippets_path(current_user), title: "Your snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'Your snippets' do
+ %i.fa.fa-clipboard
- if current_user.is_admin?
%li
= link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
- %i.icon-cogs
+ %i.fa.fa-cogs
- if current_user.can_create_project?
%li
= link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do
- %i.icon-plus
+ %i.fa.fa-plus
%li
= link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do
- %i.icon-user
+ %i.fa.fa-user
%li
- = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
- %i.icon-signout
+ = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Sign out", class: 'has_bottom_tooltip', 'data-original-title' => 'Sign out' do
+ %i.fa.fa-sign-out
%li.hidden-xs
- = link_to current_user, class: "profile-pic", id: 'profile-pic' do
- = image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
+ = link_to current_user, class: "profile-pic has_bottom_tooltip", id: 'profile-pic', 'data-original-title' => 'Your profile' do
+ = image_tag avatar_icon(current_user.email, 60), alt: 'User activity'
+
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 353f7ce34f..3c58f10e75 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,3 +1,3 @@
:javascript
- GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project, type: @noteable.class, type_id: params[:id])}"
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(@project.namespace, @project, type: @noteable.class, type_id: params[:id])}"
GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
new file mode 100644
index 0000000000..422966cdc5
--- /dev/null
+++ b/app/views/layouts/_page.html.haml
@@ -0,0 +1,23 @@
+- if defined?(sidebar)
+ .page-with-sidebar{ class: nav_sidebar_class }
+ = render "layouts/broadcast"
+ .sidebar-wrapper
+ = render(sidebar)
+ .collapse-nav
+ = render partial: 'layouts/collapse_button'
+ .content-wrapper
+ .container-fluid
+ .content
+ = render "layouts/flash"
+ .clearfix
+ = yield
+- else
+ .container.navless-container
+ .content
+ = yield
+
+= yield :embedded_scripts
+
+:coffeescript
+ $('.page-sidebar-collapsed .nav-sidebar a').tooltip placement: "right"
+
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
index 945c66e2ad..3d6d2bfc00 100644
--- a/app/views/layouts/_public_head_panel.html.haml
+++ b/app/views/layouts/_public_head_panel.html.haml
@@ -1,22 +1,22 @@
-%header.navbar.navbar-static-top.navbar-gitlab
+%header.navbar.navbar-fixed-top.navbar-gitlab
.navbar-inner
.container
%div.app_logo
- %span.separator
= link_to explore_root_path, class: "home" do
- %h1 GITLAB
- %span.separator
+ = brand_header_logo
%h1.title= title
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
%span.sr-only Toggle navigation
- %i.icon-reorder
+ %i.fa.fa-bars
- .pull-right.hidden-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new'
+ - unless current_controller?('sessions')
+ .pull-right.hidden-xs
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10'
- .navbar-collapse.collapse
- %ul.nav.navbar-nav
- %li.visible-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav
+ %li.visible-xs
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index caf0e39234..04f7984685 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -4,7 +4,25 @@
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
- = hidden_field_tag :search_code, true
+
+ - if current_controller?(:issues)
+ = hidden_field_tag :scope, 'issues'
+ - elsif current_controller?(:merge_requests)
+ = hidden_field_tag :scope, 'merge_requests'
+ - elsif current_controller?(:wikis)
+ = hidden_field_tag :scope, 'wiki_blobs'
+ - else
+ = hidden_field_tag :search_code, true
+
+ - if @snippet || @snippets
+ = hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
- = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test'
+ = button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
+
+:javascript
+ $('.search-input').on('keyup', function(e) {
+ if (e.keyCode == 27) {
+ $('.search-input').blur()
+ }
+ })
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 1ea91a1914..ab84e87c30 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,13 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Admin area"
- %body{class: "#{app_theme} admin", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "Admin area"
- = render "layouts/flash"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/admin'
-
- .container
- .content= yield
- = yield :embedded_scripts
+ %body{class: "#{app_theme} admin", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: link_to("Admin area", admin_root_path)
+ = render 'layouts/page', sidebar: 'layouts/nav/admin'
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 511db389e0..6bd8ac4adb 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,12 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Dashboard"
- %body{class: "#{app_theme} application", :'data-page' => body_data_page }
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "Dashboard"
- = render "layouts/flash"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/dashboard'
-
- .container
- .content= yield
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page }
+ = render "layouts/head_panel", title: link_to("Dashboard", root_path)
+ = render 'layouts/page', sidebar: 'layouts/nav/dashboard'
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index dd70836cdc..6f805f1c9d 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,35 +1,32 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body.ui_basic.login-page
- = render "layouts/flash"
- .container
+ %body.ui_mars.login-page.application
+ = render "layouts/broadcast"
+ = render "layouts/public_head_panel", title: ''
+ .container.navless-container
.content
- .login-title
- %h1= brand_title
- %hr
- .container
- .content
- .row
- .col-md-7
- - if brand_item
- .brand-image
- = brand_image
- .brand_text
- = brand_text
- - else
- .brand-image.default-brand-image.hidden-sm.hidden-xs
- = image_tag 'brand_logo.png'
- .brand_text.hidden-xs
- %h2 Open source software to collaborate on code
-
- %p.lead
- Manage git repositories with fine grained access controls that keep your code secure.
- Perform code reviews and enhance collaboration with merge requests.
- Each project can also have an issue tracker and a wiki.
-
- .col-md-5
+ = render "layouts/flash"
+ .row.prepend-top-20
+ .col-sm-5.pull-right
= yield
+ .col-sm-7.brand-holder.pull-left
+ %h1
+ = brand_title
+ - if brand_item
+ = brand_image
+ = brand_text
+ - else
+ %h3 Open source software to collaborate on code
+
+ %p
+ Manage git repositories with fine grained access controls that keep your code secure.
+ Perform code reviews and enhance collaboration with merge requests.
+ Each project can also have an issue tracker and a wiki.
+
+ - if extra_sign_in_text.present?
+ = markdown(extra_sign_in_text)
+
%hr
.container
.footer-links
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index d0e276d751..e51fd4cb82 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,9 +1,9 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Error"
- %body{class: "#{app_theme} application"}
+ %body{class: "#{app_theme} application"}
= render "layouts/head_panel", title: "" if current_user
- = render "layouts/flash"
.container.navless-container
+ = render "layouts/flash"
.error-page
= yield
diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml
index d023846c5e..2bd0b8d85c 100644
--- a/app/views/layouts/explore.html.haml
+++ b/app/views/layouts/explore.html.haml
@@ -2,12 +2,12 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: page_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- if current_user
- = render "layouts/head_panel", title: page_title
+ = render "layouts/head_panel", title: link_to(page_title, explore_root_path)
- else
- = render "layouts/public_head_panel", title: page_title
+ = render "layouts/public_head_panel", title: link_to(page_title, explore_root_path)
.container.navless-container
.content
.explore-title
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index fb4a3a3ba9..f4a6bee15f 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,12 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: group_head_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "group: #{@group.name}"
- = render "layouts/flash"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/group'
-
- .container
- .content= yield
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: link_to(@group.name, group_path(@group))
+ = render 'layouts/page', sidebar: 'layouts/nav/group'
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index c57216f01c..34efceb37d 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,19 +1,65 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do
- Overview
+ %i.fa.fa-dashboard
+ %span
+ Overview
= nav_link(controller: :projects) do
- = link_to "Projects", admin_projects_path
+ = link_to admin_namespaces_projects_path, title: 'Projects' do
+ %i.fa.fa-cube
+ %span
+ Projects
= nav_link(controller: :users) do
- = link_to "Users", admin_users_path
+ = link_to admin_users_path, title: 'Users' do
+ %i.fa.fa-user
+ %span
+ Users
= nav_link(controller: :groups) do
- = link_to "Groups", admin_groups_path
+ = link_to admin_groups_path, title: 'Groups' do
+ %i.fa.fa-group
+ %span
+ Groups
+ = nav_link(controller: :deploy_keys) do
+ = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
+ %i.fa.fa-key
+ %span
+ Deploy Keys
= nav_link(controller: :logs) do
- = link_to "Logs", admin_logs_path
+ = link_to admin_logs_path, title: 'Logs' do
+ %i.fa.fa-file-text
+ %span
+ Logs
= nav_link(controller: :broadcast_messages) do
- = link_to "Messages", admin_broadcast_messages_path
+ = link_to admin_broadcast_messages_path, title: 'Broadcast Messages' do
+ %i.fa.fa-bullhorn
+ %span
+ Messages
= nav_link(controller: :hooks) do
- = link_to "Hooks", admin_hooks_path
+ = link_to admin_hooks_path, title: 'Hooks' do
+ %i.fa.fa-external-link
+ %span
+ Hooks
= nav_link(controller: :background_jobs) do
- = link_to "Background Jobs", admin_background_jobs_path
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
+ %i.fa.fa-cog
+ %span
+ Background Jobs
+
+ = nav_link(controller: :applications) do
+ = link_to admin_applications_path, title: 'Applications' do
+ %i.fa.fa-cloud
+ %span
+ Applications
+
+ = nav_link(controller: :services) do
+ = link_to admin_application_settings_services_path, title: 'Service Templates' do
+ %i.fa.fa-copy
+ %span
+ Service Templates
+
+ = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
+ = link_to admin_application_settings_path, title: 'Settings' do
+ %i.fa.fa-cogs
+ %span
+ Settings
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index a300bbc190..e4f630c6a1 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,18 +1,38 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
- = link_to root_path, title: "Home" do
- Activity
- = nav_link(path: 'dashboard#projects') do
- = link_to projects_dashboard_path do
- Projects
+ = link_to root_path, title: 'Home', class: 'shortcuts-activity' do
+ %i.fa.fa-dashboard
+ %span
+ Your Projects
+ = nav_link(path: 'projects#starred') do
+ = link_to starred_dashboard_projects_path, title: 'Starred Projects' do
+ %i.fa.fa-star
+ %span
+ Starred Projects
+ = nav_link(controller: :groups) do
+ = link_to dashboard_groups_path, title: 'Groups' do
+ %i.fa.fa-group
+ %span
+ Groups
+ = nav_link(controller: :milestones) do
+ = link_to dashboard_milestones_path, title: 'Milestones' do
+ %i.fa.fa-clock-o
+ %span
+ Milestones
= nav_link(path: 'dashboard#issues') do
- = link_to issues_dashboard_path do
- Issues
- %span.count= current_user.assigned_issues.opened.count
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ %span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
- = link_to merge_requests_dashboard_path do
- Merge Requests
- %span.count= current_user.assigned_merge_requests.opened.count
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ %span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :help) do
- = link_to "Help", help_path
-
+ = link_to help_path, title: 'Help' do
+ %i.fa.fa-question-circle
+ %span
+ Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 9095a843c9..f0d92b7a12 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,25 +1,42 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: "Home" do
- Activity
- = nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group) do
- Milestones
+ %i.fa.fa-dashboard
+ %span
+ Activity
+ - if current_user
+ = nav_link(controller: [:group, :milestones]) do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
+ %i.fa.fa-clock-o
+ %span
+ Milestones
= nav_link(path: 'groups#issues') do
- = link_to issues_group_path(@group) do
- Issues
- - if current_user
- %span.count= current_user.assigned_issues.opened.of_group(@group).count
+ = link_to issues_group_path(@group), title: 'Issues' do
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ - if current_user
+ %span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group) do
- Merge Requests
- - if current_user
- %span.count= current_user.cared_merge_requests.opened.of_group(@group).count
- = nav_link(path: 'groups#members') do
- = link_to "Members", members_group_path(@group)
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ - if current_user
+ %span.count= MergeRequest.opened.of_group(@group).count
+ = nav_link(controller: [:group_members]) do
+ = link_to group_group_members_path(@group), title: 'Members' do
+ %i.fa.fa-users
+ %span
+ Members
- - if can?(current_user, :manage_group, @group)
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), class: "tab " do
- Settings
+ - if can?(current_user, :admin_group, @group)
+ = nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do
+ = link_to edit_group_path(@group), title: 'Settings', class: "tab no-highlight" do
+ %i.fa.fa-cogs
+ %span
+ Settings
+ %i.fa.fa-angle-down
+ - if group_settings_page?
+ = render 'groups/settings_nav'
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 1de5ee99cf..d88e862829 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,26 +1,50 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: "Profile" do
- Profile
+ %i.fa.fa-user
+ %span
+ Profile
= nav_link(controller: :accounts) do
- = link_to "Account", profile_account_path
+ = link_to profile_account_path, title: 'Account' do
+ %i.fa.fa-gear
+ %span
+ Account
+ = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do
+ = link_to applications_profile_path, title: 'Applications' do
+ %i.fa.fa-cloud
+ %span
+ Applications
= nav_link(controller: :emails) do
- = link_to profile_emails_path do
- Emails
- %span.count= current_user.emails.count + 1
+ = link_to profile_emails_path, title: 'Emails' do
+ %i.fa.fa-envelope-o
+ %span
+ Emails
+ %span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
- = link_to "Password", edit_profile_password_path
+ = link_to edit_profile_password_path, title: 'Password' do
+ %i.fa.fa-lock
+ %span
+ Password
= nav_link(controller: :notifications) do
- = link_to "Notifications", profile_notifications_path
- = nav_link(controller: :keys) do
- = link_to profile_keys_path do
- SSH Keys
- %span.count= current_user.keys.count
- = nav_link(path: 'profiles#design') do
- = link_to "Design", design_profile_path
- = nav_link(controller: :groups) do
- = link_to "Groups", profile_groups_path
- = nav_link(path: 'profiles#history') do
- = link_to "History", history_profile_path
+ = link_to profile_notifications_path, title: 'Notifications' do
+ %i.fa.fa-inbox
+ %span
+ Notifications
+ = nav_link(controller: :keys) do
+ = link_to profile_keys_path, title: 'SSH Keys' do
+ %i.fa.fa-key
+ %span
+ SSH Keys
+ %span.count= current_user.keys.count
+ = nav_link(path: 'profiles#design') do
+ = link_to design_profile_path, title: 'Design' do
+ %i.fa.fa-image
+ %span
+ Design
+ = nav_link(path: 'profiles#history') do
+ = link_to history_profile_path, title: 'History' do
+ %i.fa.fa-history
+ %span
+ History
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 69491c2529..6c13f30f62 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,46 +1,97 @@
-%ul
- = nav_link(path: 'projects#show', html_options: {class: "home"}) do
- = link_to project_path(@project), title: "Project" do
- Activity
+%ul.project-navigation.nav.nav-sidebar
+ - if @project_settings_nav
+ = nav_link do
+ = link_to project_path(@project), title: 'Back to project', class: "" do
+ %i.fa.fa-caret-square-o-left
+ %span
+ Back to project
- - if project_nav_tab? :files
- = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
- = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref)
+ %li.separate-item
- - if project_nav_tab? :commits
- = nav_link(controller: %w(commit commits compare repositories tags branches)) do
- = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref)
+ = render 'projects/settings_nav'
- - if project_nav_tab? :network
- = nav_link(controller: %w(network)) do
- = link_to "Network", project_network_path(@project, @ref || @repository.root_ref)
+ - else
+ = nav_link(path: 'projects#show', html_options: {class: "home"}) do
+ = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
+ %i.fa.fa-dashboard
+ %span
+ Project
+ - if project_nav_tab? :files
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree' do
+ %i.fa.fa-files-o
+ %span
+ Files
- - if project_nav_tab? :graphs
- = nav_link(controller: %w(graphs)) do
- = link_to "Graphs", project_graph_path(@project, @ref || @repository.root_ref)
+ - if project_nav_tab? :commits
+ = nav_link(controller: %w(commit commits compare repositories tags branches)) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits' do
+ %i.fa.fa-history
+ %span
+ Commits
- - if project_nav_tab? :issues
- = nav_link(controller: %w(issues milestones labels)) do
- = link_to url_for_project_issues do
- Issues
- - if @project.used_default_issues_tracker?
- %span.count.issue_counter= @project.issues.opened.count
+ - if project_nav_tab? :network
+ = nav_link(controller: %w(network)) do
+ = link_to namespace_project_network_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network' do
+ %i.fa.fa-code-fork
+ %span
+ Network
- - if project_nav_tab? :merge_requests
- = nav_link(controller: :merge_requests) do
- = link_to project_merge_requests_path(@project) do
- Merge Requests
- %span.count.merge_counter= @project.merge_requests.opened.count
+ - if project_nav_tab? :graphs
+ = nav_link(controller: %w(graphs)) do
+ = link_to namespace_project_graph_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs' do
+ %i.fa.fa-area-chart
+ %span
+ Graphs
- - if project_nav_tab? :wiki
- = nav_link(controller: :wikis) do
- = link_to 'Wiki', project_wiki_path(@project, :home)
+ - if project_nav_tab? :milestones
+ = nav_link(controller: :milestones) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ %i.fa.fa-clock-o
+ %span
+ Milestones
- - if project_nav_tab? :snippets
- = nav_link(controller: :snippets) do
- = link_to 'Snippets', project_snippets_path(@project)
+ - if project_nav_tab? :issues
+ = nav_link(controller: :issues) do
+ = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ - if @project.default_issues_tracker?
+ %span.count.issue_counter= @project.issues.opened.count
- - if project_nav_tab? :settings
- = nav_link(html_options: {class: "#{project_tab_class}"}) do
- = link_to edit_project_path(@project), class: "stat-tab tab " do
- Settings
+ - if project_nav_tab? :merge_requests
+ = nav_link(controller: :merge_requests) do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ %span.count.merge_counter= @project.merge_requests.opened.count
+
+ - if project_nav_tab? :labels
+ = nav_link(controller: :labels) do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ %i.fa.fa-tags
+ %span
+ Labels
+
+ - if project_nav_tab? :wiki
+ = nav_link(controller: :wikis) do
+ = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
+ %i.fa.fa-book
+ %span
+ Wiki
+
+ - if project_nav_tab? :snippets
+ = nav_link(controller: :snippets) do
+ = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
+ %i.fa.fa-file-text-o
+ %span
+ Snippets
+
+ - if project_nav_tab? :settings
+ = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
+ = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do
+ %i.fa.fa-cogs
+ %span
+ Settings
diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml
index c43d688a2c..4d0278251a 100644
--- a/app/views/layouts/navless.html.haml
+++ b/app/views/layouts/navless.html.haml
@@ -1,11 +1,10 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- = render "layouts/head_panel", title: @title
- = render "layouts/flash"
-
+ = render "layouts/head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title
.container.navless-container
.content
+ = render "layouts/flash"
= yield
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index ab421d63f1..00c7cedce4 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -16,7 +16,18 @@
font-size:small;
color:#777
}
-
+ pre.commit-message {
+ white-space: pre-wrap;
+ }
+ .file-stats a {
+ text-decoration: none;
+ }
+ .file-stats .new-file {
+ color: #090;
+ }
+ .file-stats .deleted-file {
+ color: #B00;
+ }}
%body
%div.content
= yield
@@ -24,7 +35,8 @@
%p
\—
%br
- - if @project
- You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team.
- if @target_url
#{link_to "View it on GitLab", @target_url}
+ = email_action @target_url
+ - if @project && !@disable_footer
+ You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team.
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 2d869a6cdc..2b5be7fc37 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,12 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Profile"
- %body{class: "#{app_theme} profile", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "Profile"
- = render "layouts/flash"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/profile'
-
- .container
- .content= yield
+ %body{class: "#{app_theme} profile", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: link_to("Profile", profile_path)
+ = render 'layouts/page', sidebar: 'layouts/nav/profile'
diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml
index 5659cfab31..0a0039dec1 100644
--- a/app/views/layouts/project_settings.html.haml
+++ b/app/views/layouts/project_settings.html.haml
@@ -1,21 +1,8 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
- %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
- = render "layouts/broadcast"
+ %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
- = render "layouts/flash"
- - if can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
-
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
-
- .container
- .content
- .row
- .col-md-2
- = render "projects/settings_nav"
- .col-md-10
- = yield
+ - @project_settings_nav = true
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml
index f02eca6bd7..dde0964f47 100644
--- a/app/views/layouts/projects.html.haml
+++ b/app/views/layouts/projects.html.haml
@@ -1,17 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: project_head_title
- %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
- = render "layouts/broadcast"
+ %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
- = render "layouts/flash"
- - if can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
-
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
-
- .container
- .content= yield
- = yield :embedded_scripts
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml
index a289b78472..b9b1d03e08 100644
--- a/app/views/layouts/public_group.html.haml
+++ b/app/views/layouts/public_group.html.haml
@@ -1,10 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: group_head_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/public_head_panel", title: "group: #{@group.name}"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/group'
- .container
- .content= yield
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/public_head_panel", title: link_to(@group.name, group_path(@group))
+ = render 'layouts/page', sidebar: 'layouts/nav/group'
diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml
index 2a9230244f..04fa7c84e7 100644
--- a/app/views/layouts/public_projects.html.haml
+++ b/app/views/layouts/public_projects.html.haml
@@ -1,10 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/public_head_panel", title: project_title(@project)
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
- .container
- .content= yield
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml
index 4aa258fea0..71c16bd168 100644
--- a/app/views/layouts/public_users.html.haml
+++ b/app/views/layouts/public_users.html.haml
@@ -1,8 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/public_head_panel", title: @title
- .container.navless-container
- .content= yield
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/public_head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title
+ = render 'layouts/page'
diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml
index 97ed8ba12d..f9d8db06e1 100644
--- a/app/views/layouts/search.html.haml
+++ b/app/views/layouts/search.html.haml
@@ -1,11 +1,10 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Search"
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- = render "layouts/head_panel", title: "Search"
- = render "layouts/flash"
-
+ = render "layouts/head_panel", title: link_to("Search", search_path)
.container.navless-container
.content
+ = render "layouts/flash"
= yield
diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml
deleted file mode 100644
index ce13853ed7..0000000000
--- a/app/views/layouts/user_team.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-!!! 5
-%html{ lang: "en"}
- = render "layouts/head", title: "#{@team.name}"
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "team: #{@team.name}"
- = render "layouts/flash"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/team'
-
- .container
- .content= yield
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 5272dfa0ed..3fd4b04ac8 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,2 +1,2 @@
%div
- = markdown(@note.note)
+ = markdown(@note.note, reference_only_path: false)
diff --git a/app/views/notify/_reassigned_issuable_email.html.haml b/app/views/notify/_reassigned_issuable_email.html.haml
new file mode 100644
index 0000000000..56d81b2ed2
--- /dev/null
+++ b/app/views/notify/_reassigned_issuable_email.html.haml
@@ -0,0 +1,10 @@
+%p
+ Assignee changed
+ - if @previous_assignee
+ from
+ %strong #{@previous_assignee.name}
+ to
+ - if issuable.assignee_id
+ %strong #{issuable.assignee_name}
+ - else
+ %strong Unassigned
diff --git a/app/views/notify/_reassigned_issuable_email.text.erb b/app/views/notify/_reassigned_issuable_email.text.erb
new file mode 100644
index 0000000000..855d37429d
--- /dev/null
+++ b/app/views/notify/_reassigned_issuable_email.text.erb
@@ -0,0 +1,6 @@
+Reassigned <%= issuable.class.model_name.human.titleize %> <%= issuable.iid %>
+
+<%= url_for([issuable.project.namespace.becomes(Namespace), issuable.project, issuable, {only_path: false}]) %>
+
+Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee -%>
+ to <%= "#{issuable.assignee_id ? issuable.assignee_name : 'Unassigned'}" %>
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index 49f160a0d5..ac703b31ed 100644
--- a/app/views/notify/closed_issue_email.text.haml
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -1,3 +1,3 @@
= "Issue was closed by #{@updated_by.name}"
-Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
+Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index d6b76e906c..59db86b08b 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,6 +1,6 @@
= "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/group_access_granted_email.html.haml b/app/views/notify/group_access_granted_email.html.haml
index 823ebf7734..f1916d624b 100644
--- a/app/views/notify/group_access_granted_email.html.haml
+++ b/app/views/notify/group_access_granted_email.html.haml
@@ -1,4 +1,4 @@
%p
- = "You have been granted #{@membership.human_access} access to group"
+ = "You have been granted #{@group_member.human_access} access to group"
= link_to group_url(@group) do
= @group.name
diff --git a/app/views/notify/group_access_granted_email.text.erb b/app/views/notify/group_access_granted_email.text.erb
index 331bb98d5c..ef9617bfc1 100644
--- a/app/views/notify/group_access_granted_email.text.erb
+++ b/app/views/notify/group_access_granted_email.text.erb
@@ -1,4 +1,4 @@
-You have been granted <%= @membership.human_access %> access to group <%= @group.name %>
+You have been granted <%= @group_member.human_access %> access to group <%= @group.name %>
<%= url_for(group_url(@group)) %>
diff --git a/app/views/notify/group_invite_accepted_email.html.haml b/app/views/notify/group_invite_accepted_email.html.haml
new file mode 100644
index 0000000000..55efad384a
--- /dev/null
+++ b/app/views/notify/group_invite_accepted_email.html.haml
@@ -0,0 +1,6 @@
+%p
+ #{@group_member.invite_email}, now known as
+ #{link_to @group_member.user.name, user_url(@group_member.user)},
+ has accepted your invitation to join group
+ #{link_to @group.name, group_url(@group)}.
+
diff --git a/app/views/notify/group_invite_accepted_email.text.erb b/app/views/notify/group_invite_accepted_email.text.erb
new file mode 100644
index 0000000000..f8b70f7a5a
--- /dev/null
+++ b/app/views/notify/group_invite_accepted_email.text.erb
@@ -0,0 +1,3 @@
+<%= @group_member.invite_email %>, now known as <%= @group_member.user.name %>, has accepted your invitation to join group <%= @group.name %>.
+
+<%= group_url(@group) %>
diff --git a/app/views/notify/group_invite_declined_email.html.haml b/app/views/notify/group_invite_declined_email.html.haml
new file mode 100644
index 0000000000..f9525d84fa
--- /dev/null
+++ b/app/views/notify/group_invite_declined_email.html.haml
@@ -0,0 +1,5 @@
+%p
+ #{@invite_email}
+ has declined your invitation to join group
+ #{link_to @group.name, group_url(@group)}.
+
diff --git a/app/views/notify/group_invite_declined_email.text.erb b/app/views/notify/group_invite_declined_email.text.erb
new file mode 100644
index 0000000000..6c19a288d1
--- /dev/null
+++ b/app/views/notify/group_invite_declined_email.text.erb
@@ -0,0 +1,3 @@
+<%= @invite_email %> has declined your invitation to join group <%= @group.name %>.
+
+<%= group_url(@group) %>
diff --git a/app/views/notify/group_member_invited_email.html.haml b/app/views/notify/group_member_invited_email.html.haml
new file mode 100644
index 0000000000..163e88bfea
--- /dev/null
+++ b/app/views/notify/group_member_invited_email.html.haml
@@ -0,0 +1,14 @@
+%p
+ You have been invited
+ - if inviter = @group_member.created_by
+ by
+ = link_to inviter.name, user_url(inviter)
+ to join group
+ = link_to @group.name, group_url(@group)
+ as #{@group_member.human_access}.
+
+%p
+ = link_to 'Accept invitation', invite_url(@token)
+ or
+ = link_to 'decline', decline_invite_url(@token)
+
diff --git a/app/views/notify/group_member_invited_email.text.erb b/app/views/notify/group_member_invited_email.text.erb
new file mode 100644
index 0000000000..28ce4819b1
--- /dev/null
+++ b/app/views/notify/group_member_invited_email.text.erb
@@ -0,0 +1,4 @@
+You have been invited <%= "by #{@group_member.created_by.name} " if @group_member.created_by %>to join group <%= @group.name %> as <%= @group_member.human_access %>.
+
+Accept invitation: <%= invite_url(@token) %>
+Decline invitation: <%= decline_invite_url(@token) %>
diff --git a/app/views/notify/issue_status_changed_email.text.erb b/app/views/notify/issue_status_changed_email.text.erb
index 4200881f7e..e6ab3fcde7 100644
--- a/app/views/notify/issue_status_changed_email.text.erb
+++ b/app/views/notify/issue_status_changed_email.text.erb
@@ -1,4 +1,4 @@
Issue was <%= @issue_status %> by <%= @updated_by.name %>
-Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index 8750bf86e2..b96dd0fd8a 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,6 +1,6 @@
= "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}"
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 360da60bc3..9db75bdb19 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,6 +1,6 @@
= "Merge Request ##{@merge_request.iid} was merged"
-Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request Url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index f2f8eee18c..53a068be52 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,5 +1,5 @@
-if @issue.description
- = markdown(@issue.description)
+ = markdown(@issue.description, reference_only_path: false)
- if @issue.assignee_id.present?
%p
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index d36f54eb1c..0cc6293549 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -1,5 +1,5 @@
New Issue was created.
-Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Asignee: <%= @issue.assignee_name %>
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index f02d5111b2..5b7dd117c1 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
-if @merge_request.description
- = markdown(@merge_request.description)
+ = markdown(@merge_request.description, reference_only_path: false)
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 16be4bb619..f08039ad04 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,6 +1,6 @@
New Merge Request #<%= @merge_request.iid %>
-<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
+<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml
index deb0822d8f..63b0cbbd20 100644
--- a/app/views/notify/new_ssh_key_email.html.haml
+++ b/app/views/notify/new_ssh_key_email.html.haml
@@ -6,5 +6,5 @@
title:
%code= @key.title
%p
- If this key was added in error, you can remove it here:
+ If this key was added in error, you can remove it under
= link_to "SSH Keys", profile_keys_url
diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb
index 5f0080c2b7..05b551c89a 100644
--- a/app/views/notify/new_ssh_key_email.text.erb
+++ b/app/views/notify/new_ssh_key_email.text.erb
@@ -2,6 +2,6 @@ Hi <%= @user.name %>!
A new public key was added to your account:
-title.................. <%= @key.title %>
+Title: <%= @key.title %>
-If this key was added in error, you can remove it here: <%= profile_keys_url %>
+If this key was added in error, you can remove it at <%= profile_keys_url %>
diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb
index aab8e5cfb6..aaeaf5fdf7 100644
--- a/app/views/notify/note_commit_email.text.erb
+++ b/app/views/notify/note_commit_email.text.erb
@@ -1,6 +1,6 @@
New comment for Commit <%= @commit.short_id %>
-<%= url_for(project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")) %>
+<%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %>
Author: <%= @note.author_name %>
diff --git a/app/views/notify/note_issue_email.text.erb b/app/views/notify/note_issue_email.text.erb
index 8a61f54a33..e33cbcd70f 100644
--- a/app/views/notify/note_issue_email.text.erb
+++ b/app/views/notify/note_issue_email.text.erb
@@ -1,6 +1,6 @@
New comment for Issue <%= @issue.iid %>
-<%= url_for(project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")) %>
+<%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue, anchor: "note_#{@note.id}")) %>
Author: <%= @note.author_name %>
diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb
index 79e72ca16c..1d1411992a 100644
--- a/app/views/notify/note_merge_request_email.text.erb
+++ b/app/views/notify/note_merge_request_email.text.erb
@@ -1,6 +1,6 @@
New comment for Merge Request <%= @merge_request.iid %>
-<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
+<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
<%= @note.author_name %>
diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml
index ce34f82535..dfc30a2d36 100644
--- a/app/views/notify/project_access_granted_email.html.haml
+++ b/app/views/notify/project_access_granted_email.html.haml
@@ -1,5 +1,5 @@
%p
- = "You have been granted #{@users_project.human_access} access to project"
+ = "You have been granted #{@project_member.human_access} access to project"
%p
- = link_to project_url(@project) do
+ = link_to namespace_project_url(@project.namespace, @project) do
= @project.name_with_namespace
diff --git a/app/views/notify/project_access_granted_email.text.erb b/app/views/notify/project_access_granted_email.text.erb
index 66c57def37..68eb1611ba 100644
--- a/app/views/notify/project_access_granted_email.text.erb
+++ b/app/views/notify/project_access_granted_email.text.erb
@@ -1,4 +1,4 @@
-You have been granted <%= @users_project.human_access %> access to project <%= @project.name_with_namespace %>
+You have been granted <%= @project_member.human_access %> access to project <%= @project.name_with_namespace %>
-<%= url_for(project_url(@project)) %>
+<%= url_for(namespace_project_url(@project.namespace, @project)) %>
diff --git a/app/views/notify/project_invite_accepted_email.html.haml b/app/views/notify/project_invite_accepted_email.html.haml
new file mode 100644
index 0000000000..7e58d30b10
--- /dev/null
+++ b/app/views/notify/project_invite_accepted_email.html.haml
@@ -0,0 +1,6 @@
+%p
+ #{@project_member.invite_email}, now known as
+ #{link_to @project_member.user.name, user_url(@project_member.user)},
+ has accepted your invitation to join project
+ #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}.
+
diff --git a/app/views/notify/project_invite_accepted_email.text.erb b/app/views/notify/project_invite_accepted_email.text.erb
new file mode 100644
index 0000000000..fcbe752114
--- /dev/null
+++ b/app/views/notify/project_invite_accepted_email.text.erb
@@ -0,0 +1,3 @@
+<%= @project_member.invite_email %>, now known as <%= @project_member.user.name %>, has accepted your invitation to join project <%= @project.name_with_namespace %>.
+
+<%= namespace_project_url(@project.namespace, @project) %>
diff --git a/app/views/notify/project_invite_declined_email.html.haml b/app/views/notify/project_invite_declined_email.html.haml
new file mode 100644
index 0000000000..c2d7e6f6e3
--- /dev/null
+++ b/app/views/notify/project_invite_declined_email.html.haml
@@ -0,0 +1,5 @@
+%p
+ #{@invite_email}
+ has declined your invitation to join project
+ #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}.
+
diff --git a/app/views/notify/project_invite_declined_email.text.erb b/app/views/notify/project_invite_declined_email.text.erb
new file mode 100644
index 0000000000..484687fa51
--- /dev/null
+++ b/app/views/notify/project_invite_declined_email.text.erb
@@ -0,0 +1,3 @@
+<%= @invite_email %> has declined your invitation to join project <%= @project.name_with_namespace %>.
+
+<%= namespace_project_url(@project.namespace, @project) %>
diff --git a/app/views/notify/project_member_invited_email.html.haml b/app/views/notify/project_member_invited_email.html.haml
new file mode 100644
index 0000000000..79eb89616d
--- /dev/null
+++ b/app/views/notify/project_member_invited_email.html.haml
@@ -0,0 +1,13 @@
+%p
+ You have been invited
+ - if inviter = @project_member.created_by
+ by
+ = link_to inviter.name, user_url(inviter)
+ to join project
+ = link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)
+ as #{@project_member.human_access}.
+
+%p
+ = link_to 'Accept invitation', invite_url(@token)
+ or
+ = link_to 'decline', decline_invite_url(@token)
diff --git a/app/views/notify/project_member_invited_email.text.erb b/app/views/notify/project_member_invited_email.text.erb
new file mode 100644
index 0000000000..e070627211
--- /dev/null
+++ b/app/views/notify/project_member_invited_email.text.erb
@@ -0,0 +1,4 @@
+You have been invited <%= "by #{@project_member.created_by.name} " if @project_member.created_by %>to join project <%= @project.name_with_namespace %> as <%= @project_member.human_access %>.
+
+Accept invitation: <%= invite_url(@token) %>
+Decline invitation: <%= decline_invite_url(@token) %>
diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml
index 1667c59bc0..3cd759f1f5 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -1,15 +1,15 @@
%p
- = "Project was moved to another location"
+ Project was moved to another location
%p
The project is now located under
- = link_to project_url(@project) do
+ = link_to namespace_project_url(@project.namespace, @project) do
= @project.name_with_namespace
%p
To update the remote url in your local repository run (for ssh):
-%p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" }
+%p{ style: "background: #f5f5f5; padding:10px; border:1px solid #ddd" }
git remote set-url origin #{@project.ssh_url_to_repo}
%p
or for http(s):
-%p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" }
+%p{ style: "background: #f5f5f5; padding:10px; border:1px solid #ddd" }
git remote set-url origin #{@project.http_url_to_repo}
%br
diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb
index 664148fb3b..b3f18b35a4 100644
--- a/app/views/notify/project_was_moved_email.text.erb
+++ b/app/views/notify/project_was_moved_email.text.erb
@@ -1,7 +1,7 @@
Project was moved to another location
The project is now located under
-<%= project_url(@project) %>
+<%= namespace_project_url(@project.namespace, @project) %>
To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/reassigned_issue_email.html.haml b/app/views/notify/reassigned_issue_email.html.haml
index f1458df5c7..498ba8b836 100644
--- a/app/views/notify/reassigned_issue_email.html.haml
+++ b/app/views/notify/reassigned_issue_email.html.haml
@@ -1,11 +1 @@
-%p
- Assignee changed
- - if @previous_assignee
- from
- %strong #{@previous_assignee.name}
- to
- - if @issue.assignee_id
- %strong #{@issue.assignee_name}
- - else
- %strong Unassigned
-
+= render 'reassigned_issuable_email', issuable: @issue
diff --git a/app/views/notify/reassigned_issue_email.text.erb b/app/views/notify/reassigned_issue_email.text.erb
index 4becac2749..710253be98 100644
--- a/app/views/notify/reassigned_issue_email.text.erb
+++ b/app/views/notify/reassigned_issue_email.text.erb
@@ -1,5 +1 @@
-Reassigned Issue <%= @issue.iid %>
-
-<%= url_for(project_issue_url(@issue.project, @issue)) %>
-
-Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= "#{@issue.assignee_id ? @issue.assignee_name : 'Unassigned'}" %>
+<%= render 'reassigned_issuable_email', issuable: @issue %>
diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml
index 00aee6bc95..2a650130f5 100644
--- a/app/views/notify/reassigned_merge_request_email.html.haml
+++ b/app/views/notify/reassigned_merge_request_email.html.haml
@@ -1,7 +1 @@
-%p
- Assignee changed
- - if @previous_assignee
- from
- %strong #{@previous_assignee.name}
- to
- %strong #{@merge_request.assignee_name}
+= render 'reassigned_issuable_email', issuable: @merge_request
diff --git a/app/views/notify/reassigned_merge_request_email.text.erb b/app/views/notify/reassigned_merge_request_email.text.erb
index 87a7847e06..b5b4f1ff99 100644
--- a/app/views/notify/reassigned_merge_request_email.text.erb
+++ b/app/views/notify/reassigned_merge_request_email.text.erb
@@ -1,7 +1 @@
-Reassigned Merge Request #<%= @merge_request.iid %>
-
-<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
-
-
-Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @merge_request.assignee_name %>
-
+<%= render 'reassigned_issuable_email', issuable: @merge_request %>
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 0358810afd..a374a66233 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,28 +1,66 @@
-%h3 #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
+%h3 #{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}
-%h4 Commits:
+- if @compare
+ - if @reverse_compare
+ %p
+ %strong WARNING:
+ The push did not contain any new commits, but force pushed to delete the commits and changes below.
-%ul
- - @commits.each do |commit|
- %li
- %strong #{commit.short_id}
- %span by #{commit.author_name}
- %pre #{commit.safe_message}
+ %h4
+ = @reverse_compare ? "Deleted commits:" : "Commits:"
-%h4 Changes:
-- @diffs.each do |diff|
- %li
- %strong
- - if diff.old_path == diff.new_path
- = diff.new_path
- - elsif diff.new_path && diff.old_path
- #{diff.old_path} → #{diff.new_path}
- - else
- = diff.new_path || diff.old_path
- %hr
- %pre
- = diff.diff
- %br
+ %ul
+ - @commits.each do |commit|
+ %li
+ %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)}
+ %div
+ %span by #{commit.author_name}
+ %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
+ %pre.commit-message
+ = commit.safe_message
-- if @compare.timeout
- %h5 Huge diff. To prevent performance issues changes are hidden
+ %h4 #{pluralize @diffs.count, "changed file"}:
+
+ %ul
+ - @diffs.each_with_index do |diff, i|
+ %li.file-stats
+ %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" }
+ - if diff.deleted_file
+ %span.deleted-file
+ −
+ = diff.old_path
+ - elsif diff.renamed_file
+ = diff.old_path
+ →
+ = diff.new_path
+ - elsif diff.new_file
+ %span.new-file
+ +
+ = diff.new_path
+ - else
+ = diff.new_path
+
+ - unless @disable_diffs
+ %h4 Changes:
+ - @diffs.each_with_index do |diff, i|
+ %li{id: "diff-#{i}"}
+ %a{href: @target_url + "#diff-#{i}"}
+ - if diff.deleted_file
+ %strong
+ = diff.old_path
+ deleted
+ - elsif diff.renamed_file
+ %strong
+ = diff.old_path
+ →
+ %strong
+ = diff.new_path
+ - else
+ %strong
+ = diff.new_path
+ %hr
+ = color_email_diff(diff.diff)
+ %br
+
+ - if @compare.timeout
+ %h5 Huge diff. To prevent performance issues changes are hidden
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 4d7c972a16..97a176ed2a 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -1,25 +1,49 @@
-#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
-
-\
-Commits:
-- @commits.each do |commit|
- #{commit.short_id} by #{commit.author_name}
- #{commit.safe_message}
- \- - - - -
-\
-\
-Changes:
-- @diffs.each do |diff|
+#{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{@project.name_with_namespace}
+- if @compare
\
- \=====================================
- - if diff.old_path == diff.new_path
- = diff.new_path
- - elsif diff.new_path && diff.old_path
- #{diff.old_path} → #{diff.new_path}
- - else
- = diff.new_path || diff.old_path
- \=====================================
- != diff.diff
-\
-- if @compare.timeout
- Huge diff. To prevent performance issues it was hidden
+ \
+ - if @reverse_compare
+ WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.
+ \
+ \
+ = @reverse_compare ? "Deleted commits:" : "Commits:"
+ - @commits.each do |commit|
+ #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
+ #{commit.safe_message}
+ \- - - - -
+ \
+ \
+ #{pluralize @diffs.count, "changed file"}:
+ \
+ - @diffs.each do |diff|
+ - if diff.deleted_file
+ \- − #{diff.old_path}
+ - elsif diff.renamed_file
+ \- #{diff.old_path} → #{diff.new_path}
+ - elsif diff.new_file
+ \- + #{diff.new_path}
+ - else
+ \- #{diff.new_path}
+ - unless @disable_diffs
+ \
+ \
+ Changes:
+ - @diffs.each do |diff|
+ \
+ \=====================================
+ - if diff.deleted_file
+ #{diff.old_path} deleted
+ - elsif diff.renamed_file
+ #{diff.old_path} → #{diff.new_path}
+ - else
+ = diff.new_path
+ \=====================================
+ != diff.diff
+ - if @compare.timeout
+ \
+ \
+ Huge diff. To prevent performance issues it was hidden
+ - if @target_url
+ \
+ \
+ View it on GitLab: #{@target_url}
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index c60e1ca929..5bffb4acc1 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,16 +1,11 @@
-%h3.page-title
- Account settings
-%p.light
- You can change your username and private token here.
- - if current_user.ldap_user?
+- if current_user.ldap_user?
+ .alert.alert-info
Some options are unavailable for LDAP accounts
-%hr
-
.account-page
%fieldset.update-token
%legend
- Private token
+ Reset Private token
%div
= form_for @user, url: reset_private_token_profile_path, method: :put do |f|
.data
@@ -25,7 +20,7 @@
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "form-control"
%div
- = f.submit 'Reset', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token"
+ = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
= f.submit 'Generate', class: "btn success btn-build-token"
@@ -33,17 +28,21 @@
- if show_profile_social_tab?
%fieldset
- %legend Social Accounts
- .oauth_select_holder.append-bottom-10
+ %legend Connected Accounts
+ .oauth-buttons.append-bottom-10
%p Click on icon to activate signin with one of the following services
- enabled_social_providers.each do |provider|
- %span{class: oauth_active_class(provider) }
- = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider)
+ .btn-group
+ = link_to oauth_image_tag(provider), omniauth_authorize_path(User, provider),
+ class: "btn btn-lg #{'active' if oauth_active?(provider)}"
+ - if oauth_active?(provider)
+ = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
+ %i.fa.fa-close
- if show_profile_username_tab?
%fieldset.update-username
%legend
- Username
+ Change Username
= form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
%p
Changing your username will change path to all personal projects!
@@ -52,12 +51,12 @@
.loading-gif.hide
%p
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Saving new username
%p.light
= user_url(@user)
%div
- = f.submit 'Save username', class: "btn btn-save"
+ = f.submit 'Save username', class: "btn btn-warning"
- if show_profile_remove_tab?
%fieldset.remove-account
@@ -75,3 +74,4 @@
The following groups will be abandoned. You should transfer or remove them:
%strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
= link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
new file mode 100644
index 0000000000..97e98948f3
--- /dev/null
+++ b/app/views/profiles/applications.html.haml
@@ -0,0 +1,49 @@
+%h3.page-title
+ Application Settings
+%p.light
+ OAuth2 protocol settings below.
+
+%fieldset.oauth-applications
+ %legend Your applications
+ %p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
+ - if @applications.any?
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th Clients
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td
+ - application.redirect_uri.split.each do |uri|
+ %div= uri
+ %td= application.access_tokens.count
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
+ %td= render 'doorkeeper/applications/delete_form', application: application
+
+%fieldset.oauth-authorized-applications.prepend-top-20
+ %legend Authorized applications
+
+ - if @authorized_tokens.any?
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Authorized At
+ %th Scope
+ %th
+ %tbody
+ - @authorized_apps.each do |app|
+ - token = app.authorized_tokens.order('created_at desc').first
+ %tr{:id => "application_#{app.id}"}
+ %td= app.name
+ %td= token.created_at
+ %td= token.scopes
+ %td= render 'doorkeeper/authorized_applications/delete_form', application: app
+ - else
+ %p.light You dont have any authorized applications
diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml
index 0d8075b7d4..cc00d08d03 100644
--- a/app/views/profiles/design.html.haml
+++ b/app/views/profiles/design.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
- My appearance settings
+ Design Settings
%p.light
- Appearance settings saved to your profile and available across all devices
+ Appearance settings will be saved to your profile and made available across all devices.
%hr
= form_for @user, url: profile_path, remote: true, method: :put do |f|
@@ -33,6 +33,11 @@
.prev.violet
= f.radio_button :theme_id, 5
Violet
+
+ = label_tag do
+ .prev.blue
+ = f.radio_button :theme_id, 6
+ Blue
%br
.clearfix
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index ca980db2f3..09f290429e 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,9 +1,13 @@
%h3.page-title
- My email addresses
+ Email Settings
%p.light
Your
%b Primary Email
- will be used for account notifications, avatar detection and web based operations, such as edits and merges.
+ will be used for avatar detection and web based operations, such as edits and merges.
+ %br
+ Your
+ %b Notification Email
+ will be used for account notifications.
%br
All email addresses will be used to identify your commits.
@@ -16,12 +20,16 @@
%li
%strong= @primary
%span.label.label-success Primary Email
+ - if @primary === @public_email
+ %span.label.label-info Public Email
- @emails.each do |email|
%li
%strong= email.email
+ - if email.email === @public_email
+ %span.label.label-info Public Email
%span.cgray
added #{time_ago_with_tooltip(email.created_at)}
- = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-small btn-remove pull-right'
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
%h4 Add email address
= form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f|
@@ -30,4 +38,4 @@
.col-sm-10
= f.text_field :email, class: 'form-control'
.form-actions
- = f.submit 'Add', class: 'btn btn-create'
+ = f.submit 'Add email address', class: 'btn btn-create'
diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml
deleted file mode 100644
index 2efe72b1bb..0000000000
--- a/app/views/profiles/groups/index.html.haml
+++ /dev/null
@@ -1,39 +0,0 @@
-%h3.page-title
- Group membership
- - if current_user.can_create_group?
- %span.pull-right
- = link_to new_group_path, class: "btn btn-new" do
- %i.icon-plus
- New Group
-%p.light
- Group members have access to all a group's projects
-%hr
-.panel.panel-default
- .panel-heading
- %strong Groups
- (#{@user_groups.count})
- %ul.well-list
- - @user_groups.each do |user_group|
- - group = user_group.group
- %li
- .pull-right
- - if can?(current_user, :manage_group, group)
- = link_to edit_group_path(group), class: "btn-small btn btn-grouped" do
- %i.icon-cogs
- Settings
-
- - if can?(current_user, :destroy, user_group)
- = link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do
- %i.icon-signout
- Leave
-
- = link_to group, class: 'group-name' do
- %strong= group.name
-
- as
- %strong #{user_group.human_access}
-
- %div.light
- #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
-
-= paginate @user_groups
diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml
index 3951c47b5f..b1ab433f48 100644
--- a/app/views/profiles/history.html.haml
+++ b/app/views/profiles/history.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
- Account history
+ Your Account History
%p.light
- All events created by your account are listed here
+ All events created by your account are listed below.
%hr
.profile_history
= render @events
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 81411a7565..fe5770f45c 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -1,9 +1,12 @@
-%li
- = link_to profile_key_path(key) do
- %strong= key.title
- %span
- (#{key.fingerprint})
- %span.cgray
- added #{time_ago_with_tooltip(key.created_at)}
-
- = link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
+%tr
+ %td
+ = link_to path_to_key(key, is_admin) do
+ %strong= key.title
+ %td
+ %span
+ (#{key.fingerprint})
+ %td
+ %span.cgray
+ added #{time_ago_with_tooltip(key.created_at)}
+ %td
+ = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
new file mode 100644
index 0000000000..8bac22a2e1
--- /dev/null
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -0,0 +1,22 @@
+- is_admin = defined?(admin) ? true : false
+.row
+ .col-md-4
+ .panel.panel-default
+ .panel-heading
+ SSH Key
+ %ul.well-list
+ %li
+ %span.light Title:
+ %strong= @key.title
+ %li
+ %span.light Created on:
+ %strong= @key.created_at.stamp("Aug 21, 2011")
+
+ .col-md-8
+ %p
+ %span.light Fingerprint:
+ %strong= @key.fingerprint
+ %pre.well-pre
+ = @key.key
+ .pull-right
+ = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
new file mode 100644
index 0000000000..ef0075aad3
--- /dev/null
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -0,0 +1,19 @@
+- is_admin = defined?(admin) ? true : false
+.panel.panel-default
+ - if @keys.any?
+ %table.table
+ %thead.panel-heading
+ %tr
+ %th Title
+ %th Fingerprint
+ %th Added at
+ %th
+ %tbody
+ - @keys.each do |key|
+ = render 'profiles/keys/key', key: key, is_admin: is_admin
+ - else
+ .nothing-here-block
+ - if is_admin
+ User has no ssh keys
+ - else
+ There are no SSH keys with access to your account.
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index aabfd57c28..0904c50c88 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,22 +1,10 @@
%h3.page-title
- My SSH keys
+ SSH Keys Settings
.pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
%p.light
- SSH keys allow you to establish a secure connection between your computer and GitLab
- %br
Before you can add an SSH key you need to
- = link_to "generate it", help_page_path("ssh", "README")
+ = link_to "generate it.", help_page_path("ssh", "README")
%hr
-
-.panel.panel-default
- .panel-heading
- SSH Keys (#{@keys.count})
- %ul.well-list#keys-table
- = render @keys
- - if @keys.blank?
- %li
- .nothing-here-block There are no SSH keys with access to your account.
-
-
+= render 'key_table'
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index 46b7fc7286..ccec716d0c 100644
--- a/app/views/profiles/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -8,9 +8,9 @@
$('#key_key').on('focusout', function(){
var title = $('#key_title'),
val = $('#key_key').val(),
- key_mail = val.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+|\.[a-zA-Z0-9._-]+)/gi);
+ comment = val.match(/^\S+ \S+ (.+)$/);
- if( key_mail && key_mail.length > 0 && title.val() == '' ){
- $('#key_title').val( key_mail );
+ if( comment && comment.length > 1 && title.val() == '' ){
+ $('#key_title').val( comment[1] );
}
- });
\ No newline at end of file
+ });
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index c4fc1bb269..cfd5329896 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,22 +1 @@
-.row
- .col-md-4
- .panel.panel-default
- .panel-heading
- SSH Key
- %ul.well-list
- %li
- %span.light Title:
- %strong= @key.title
- %li
- %span.light Created on:
- %strong= @key.created_at.stamp("Aug 21, 2011")
-
- .col-md-8
- %p
- %span.light Fingerprint:
- %strong= @key.fingerprint
- %pre.well-pre
- = @key.key
-
-.pull-right
- = link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
+= render "key_details"
diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml
index 218d51d31a..2c85d2a9b2 100644
--- a/app/views/profiles/notifications/_settings.html.haml
+++ b/app/views/profiles/notifications/_settings.html.haml
@@ -1,12 +1,12 @@
%li
- %span.notification-icon-holder
+ %span.notification.fa.fa-holder
- if notification.global?
= notification_icon(@notification)
- else
= notification_icon(notification)
%span.str-truncated
- - if membership.kind_of? UsersGroup
+ - if membership.kind_of? GroupMember
= link_to membership.group.name, membership.group
- else
= link_to_project(membership.project)
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index efe9c03219..273e72f8a4 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,51 +1,78 @@
%h3.page-title
- Notifications settings
+ Notifications Settings
%p.light
- GitLab uses the email specified in your profile for notifications
+ These are your global notification settings.
%hr
-= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications form-horizontal global-notifications-form' do
+
+= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f|
+ -if @user.errors.any?
+ %div.alert.alert-danger
+ %ul
+ - @user.errors.full_messages.each do |msg|
+ %li= msg
+
= hidden_field_tag :notification_type, 'global'
- = label_tag :notification_level, 'Notification level', class: 'control-label'
- .col-sm-10
- .radio
- = label_tag nil, class: '' do
- = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit'
- .level-title
- Disabled
- %p You will not get any notifications via email
+ .form-group
+ = f.label :notification_email, class: "control-label"
+ .col-sm-10
+ = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "form-control"
- .radio
- = label_tag nil, class: '' do
- = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit'
- .level-title
- Participating
- %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
+ .form-group
+ = f.label :notification_level, class: 'control-label'
+ .col-sm-10
+ .radio
+ = f.label :notification_level, value: Notification::N_DISABLED do
+ = f.radio_button :notification_level, Notification::N_DISABLED
+ .level-title
+ Disabled
+ %p You will not get any notifications via email
- .radio
- = label_tag nil, class: '' do
- = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit'
- .level-title
- Watch
- %p You will receive all notifications from projects in which you participate
+ .radio
+ = f.label :notification_level, value: Notification::N_MENTION do
+ = f.radio_button :notification_level, Notification::N_MENTION
+ .level-title
+ Mention
+ %p You will receive notifications only for comments in which you were @mentioned
+
+ .radio
+ = f.label :notification_level, value: Notification::N_PARTICIPATING do
+ = f.radio_button :notification_level, Notification::N_PARTICIPATING
+ .level-title
+ Participating
+ %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
+
+ .radio
+ = f.label :notification_level, value: Notification::N_WATCH do
+ = f.radio_button :notification_level, Notification::N_WATCH
+ .level-title
+ Watch
+ %p You will receive all notifications from projects in which you participate
+
+ .form-actions
+ = f.submit 'Save changes', class: "btn btn-create"
.clearfix
%hr
- %p
- You can also specify notification level per group or per project
- %br
- By default all projects and groups uses notification level set above
.row.all-notifications
.col-md-6
+ %p
+ You can also specify notification level per group or per project.
+ %br
+ By default, all projects and groups will use the notification level set above.
%h4 Groups:
%ul.bordered-list
- - @users_groups.each do |users_group|
- - notification = Notification.new(users_group)
- = render 'settings', type: 'group', membership: users_group, notification: notification
+ - @group_members.each do |group_member|
+ - notification = Notification.new(group_member)
+ = render 'settings', type: 'group', membership: group_member, notification: notification
.col-md-6
+ %p
+ To specify the notification level per project of a group you belong to,
+ %br
+ you need to be a member of the project itself, not only its group.
%h4 Projects:
%ul.bordered-list
- - @users_projects.each do |users_project|
- - notification = Notification.new(users_project)
- = render 'settings', type: 'project', membership: users_project, notification: notification
+ - @project_members.each do |project_member|
+ - notification = Notification.new(project_member)
+ = render 'settings', type: 'project', membership: project_member, notification: notification
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 2a7d317aa3..4b04b113e8 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,25 +1,30 @@
-%h3.page-title Password
+%h3.page-title Password Settings
%p.light
- Change your password or recover your current one.
+ - if @user.password_automatically_set?
+ Set your password.
+ - else
+ Change your password or recover your current one.
%hr
.update-password
= form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f|
%div
%p.slead
- You must provide current password in order to change it.
- %br
- After a successful password update you will be redirected to login page where you should login with your new password
+ - unless @user.password_automatically_set?
+ You must provide current password in order to change it.
+ %br
+ After a successful password update, you will be redirected to the login page where you can log in with your new password.
-if @user.errors.any?
.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
- .form-group
- = f.label :current_password, class: 'control-label'
- .col-sm-10
- = f.password_field :current_password, required: true, class: 'form-control'
- %div
- = link_to "Forgot your password?", reset_profile_password_path, method: :put
+ - unless @user.password_automatically_set?
+ .form-group
+ = f.label :current_password, class: 'control-label'
+ .col-sm-10
+ = f.password_field :current_password, required: true, class: 'form-control'
+ %div
+ = link_to "Forgot your password?", reset_profile_password_path, method: :put
.form-group
= f.label :password, 'New password', class: 'control-label'
@@ -30,4 +35,4 @@
.col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
- = f.submit 'Save password', class: "btn btn-save"
+ = f.submit 'Save password', class: "btn btn-create"
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index aef7348fd2..8bed6e0dbe 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -10,10 +10,11 @@
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
-
- .form-group
- = f.label :current_password, class: 'control-label'
- .col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
+
+ - unless @user.password_automatically_set?
+ .form-group
+ = f.label :current_password, class: 'control-label'
+ .col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
.form-group
= f.label :password, class: 'control-label'
.col-sm-10= f.password_field :password, required: true, class: 'form-control'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index e03a43cbf1..6c745e69e4 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
- Profile settings
+ Profile Settings
%p.light
- This information appears on your profile.
+ This information will appear on your profile.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
%hr
@@ -36,11 +36,16 @@
= f.text_field :email, class: "form-control", required: true
- if @user.unconfirmed_email.present?
%span.help-block
- Please click the link in the confirmation email before continuing, it was send to
+ Please click the link in the confirmation email before continuing, it was sent to
%strong #{@user.unconfirmed_email}
- else
%span.help-block We also use email for avatar detection if no avatar is uploaded.
+ .form-group
+ = f.label :public_email, class: "control-label"
+ .col-sm-10
+ = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show in profile'}, class: "form-control"
+ %span.help-block This email will be displayed on your public profile.
.form-group
= f.label :skype, class: "control-label"
.col-sm-10= f.text_field :skype, class: "form-control"
@@ -53,10 +58,13 @@
.form-group
= f.label :website_url, 'Website', class: "control-label"
.col-sm-10= f.text_field :website_url, class: "form-control"
+ .form-group
+ = f.label :location, 'Location', class: "control-label"
+ .col-sm-10= f.text_field :location, class: "form-control"
.form-group
= f.label :bio, class: "control-label"
.col-sm-10
- = f.text_area :bio, rows: 6, class: "form-control", maxlength: 250
+ = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
%span.help-block Tell us about yourself in fewer than 250 characters.
.col-md-5
@@ -77,22 +85,26 @@
%br
or change it at #{link_to "gravatar.com", "http://gravatar.com"}
%hr
- %a.choose-btn.btn.btn-small.js-choose-user-avatar-button
- %i.icon-paper-clip
+ %a.choose-btn.btn.btn-sm.js-choose-user-avatar-button
+ %i.fa.fa-paperclip
%span Choose File ...
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-user-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ .light The maximum file size allowed is 200KB.
- if @user.avatar?
%hr
- = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
+ = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
- if @user.public_profile?
- .bs-callout.bs-callout-info
+ .alert.alert-info
%h4 Public profile
%p Your profile is publicly visible because you joined public project(s)
- .form-actions
- = f.submit 'Save changes', class: "btn btn-save"
+ .row
+ .col-md-7
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/profiles/update.js.erb b/app/views/profiles/update.js.erb
index 04b5cf4827..e664ac2a52 100644
--- a/app/views/profiles/update.js.erb
+++ b/app/views/profiles/update.js.erb
@@ -1,6 +1,6 @@
// Remove body class for any previous theme, re-add current one
-$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color')
-$('body').addClass('<%= app_theme %>')
+$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color light_theme dark_theme')
+$('body').addClass('<%= app_theme %> <%= theme_type %>')
// Re-render the header to reflect the new theme
$('header').html('<%= escape_javascript(render("layouts/head_panel", title: "Profile")) %>')
diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml
new file mode 100644
index 0000000000..07d4d60276
--- /dev/null
+++ b/app/views/projects/_bitbucket_import_modal.html.haml
@@ -0,0 +1,13 @@
+%div#bitbucket_import_modal.modal.hide
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 Import projects from Bitbucket
+ .modal-body
+ To enable importing projects from Bitbucket,
+ - if current_user.admin?
+ you need to
+ - else
+ your GitLab administrator needs to
+ == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md'}.
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
new file mode 100644
index 0000000000..35f7e7bb34
--- /dev/null
+++ b/app/views/projects/_commit_button.html.haml
@@ -0,0 +1,6 @@
+.form-actions
+ .commit-button-annotation
+ = button_tag 'Commit Changes',
+ class: 'btn commit-btn js-commit-button btn-create'
+ = link_to 'Cancel', cancel_path,
+ class: 'btn btn-cancel', data: {confirm: leave_edit_message}
diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml
index e283bd2bf1..3036f11bb2 100644
--- a/app/views/projects/_dropdown.html.haml
+++ b/app/views/projects/_dropdown.html.haml
@@ -1,33 +1,37 @@
- if current_user
.dropdown.pull-right
%a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
- %i.icon-reorder
+ %i.fa.fa-bars
%ul.dropdown-menu
- if @project.issues_enabled && can?(current_user, :write_issue, @project)
%li
- = link_to url_for_new_issue, title: "New Issue" do
+ = link_to url_for_new_issue(@project, only_path: true), title: "New Issue" do
+ %i.fa.fa-fw.fa-exclamation-circle
New issue
- if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
%li
- = link_to new_project_merge_request_path(@project), title: "New Merge Request" do
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do
+ %i.fa.fa-fw.fa-tasks
New merge request
- if @project.snippets_enabled && can?(current_user, :write_snippet, @project)
%li
- = link_to new_project_snippet_path(@project), title: "New Snippet" do
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
+ %i.fa.fa-fw.fa-file-text-o
New snippet
- - if can?(current_user, :admin_team_member, @project)
+ - if can?(current_user, :admin_project_member, @project)
%li
- = link_to new_project_team_member_path(@project), title: "New project member" do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do
+ %i.fa.fa-fw.fa-users
New project member
- if can? current_user, :push_code, @project
%li.divider
%li
- = link_to new_project_branch_path(@project) do
- %i.icon-code-fork
- Git branch
+ = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ %i.fa.fa-fw.fa-code-fork
+ New branch
%li
- = link_to new_project_tag_path(@project) do
- %i.icon-tag
- Git tag
+ = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ %i.fa.fa-fw.fa-tag
+ New tag
diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml
new file mode 100644
index 0000000000..e88a0f7d68
--- /dev/null
+++ b/app/views/projects/_github_import_modal.html.haml
@@ -0,0 +1,13 @@
+%div#github_import_modal.modal.hide
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 Import projects from GitHub
+ .modal-body
+ To enable importing projects from GitHub,
+ - if current_user.admin?
+ you need to
+ - else
+ your GitLab administrator needs to
+ == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'}.
\ No newline at end of file
diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml
new file mode 100644
index 0000000000..52212b6ae0
--- /dev/null
+++ b/app/views/projects/_gitlab_import_modal.html.haml
@@ -0,0 +1,13 @@
+%div#gitlab_import_modal.modal.hide
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 Import projects from GitLab.com
+ .modal-body
+ To enable importing projects from GitLab.com,
+ - if current_user.admin?
+ you need to
+ - else
+ your GitLab administrator needs to
+ == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'}.
\ No newline at end of file
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 62348f26f0..5689bdee1c 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,36 +1,43 @@
- empty_repo = @project.empty_repo?
.project-home-panel{:class => ("empty-project" if empty_repo)}
- .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(@project.visibility_level)} project" }
- = visibility_level_icon(@project.visibility_level)
- .row
- .col-sm-6
- %h4.project-home-title
- = @project.name_with_namespace
+ .project-identicon-holder
+ = project_icon(@project, alt: '', class: 'avatar project-avatar')
+ .project-home-row.project-home-row-top
+ .project-home-desc
+ - if @project.description.present?
+ = escaped_autolink(@project.description)
+ - if can?(current_user, :admin_project, @project)
+ –
+ = link_to 'Edit', edit_namespace_project_path
+ - elsif !@project.empty_repo? && @repository.readme
+ - readme = @repository.readme
+ –
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
+ = readme.name
+ .project-repo-buttons
+ .inline.star.js-toggler-container{class: @show_star ? 'on' : ''}
+ - if current_user
+ = link_to_toggle_star('Star this project.', false)
+ = link_to_toggle_star('Unstar this project.', true)
+ - else
+ = link_to new_user_session_path, class: 'btn star-btn has_tooltip', title: 'You must sign in to star a project' do
+ %span
+ = icon('star')
+ Star
+ %span.count
+ = @project.star_count
+ - unless @project.empty_repo?
+ - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
+ .inline.fork-buttons.prepend-left-10
+ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-sm btn-default' do
+ = link_to_toggle_fork
+ - else
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-sm btn-default' do
+ = link_to_toggle_fork
- .col-sm-6
- - if current_user && !empty_repo
- .project-home-dropdown
- = render "dropdown"
- = render "shared/clone_panel"
-
- .project-home-extra.row
- .col-md-7
- .project-home-desc
- - if @project.description.present?
- = auto_link ERB::Util.html_escape(@project.description), link: :urls
- - if can?(current_user, :admin_project, @project)
- –
- %strong= link_to 'Edit', edit_project_path
- - elsif !@project.empty_repo? && @repository.readme
- - readme = @repository.readme
- –
- = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do
- = readme.name
-
- .col-md-5
- .project-home-links
- - unless empty_repo
- = link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
- = link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), project_branches_path(@project)
- = link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), project_tags_path(@project)
- %span.light.prepend-left-20= repository_size
+ .project-home-row.hidden-xs
+ - if current_user && !empty_repo
+ .project-home-dropdown
+ = render "dropdown"
+ = render "shared/clone_panel"
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
new file mode 100644
index 0000000000..e321a84974
--- /dev/null
+++ b/app/views/projects/_issuable_form.html.haml
@@ -0,0 +1,87 @@
+- if issuable.errors.any?
+ .row
+ .col-sm-10.col-sm-offset-2
+ .alert.alert-danger
+ - issuable.errors.full_messages.each do |msg|
+ %span= msg
+ %br
+.form-group
+ = f.label :title, class: 'control-label' do
+ %strong= 'Title *'
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, autofocus: true,
+ class: 'form-control pad js-gfm-input', required: true
+.form-group.issuable-description
+ = f.label :description, 'Description', class: 'control-label'
+ .col-sm-10
+
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render 'projects/zen', f: f, attr: :description,
+ classes: 'description form-control'
+ .col-sm-12.hint
+ .pull-left
+ Parsed with
+ #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
+ .pull-right
+ Attach files by dragging & dropping
+ or #{link_to 'selecting them', '#', class: 'markdown-selector' }.
+
+ .clearfix
+ .error-alert
+%hr
+.form-group
+ .issue-assignee
+ = f.label :assignee_id, class: 'control-label' do
+ %i.fa.fa-user
+ Assign to
+ .col-sm-10
+ = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
+ placeholder: 'Select a user', class: 'custom-form-control', null_user: true,
+ selected: issuable.assignee_id)
+
+ = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
+.form-group
+ .issue-milestone
+ = f.label :milestone_id, class: 'control-label' do
+ %i.fa.fa-clock-o
+ Milestone
+ .col-sm-10
+ - if milestone_options(issuable).present?
+ = f.select(:milestone_id, milestone_options(issuable),
+ { include_blank: 'Select milestone' }, { class: 'select2' })
+ - else
+ .prepend-top-10
+ %span.light No open milestones available.
+
+ - if can? current_user, :admin_milestone, issuable.project
+ = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
+.form-group
+ = f.label :label_ids, class: 'control-label' do
+ %i.fa.fa-tag
+ Labels
+ .col-sm-10
+ - if issuable.project.labels.any?
+ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+ { selected: issuable.label_ids }, multiple: true, class: 'select2'
+ - else
+ .prepend-top-10
+ %span.light No labels yet.
+
+ - if can? current_user, :admin_label, issuable.project
+ = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
+
+.form-actions
+ - if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
+ %p
+ Please review the
+ %strong #{link_to 'guidelines for contribution', guide_url}
+ to this repository.
+ - if issuable.new_record?
+ = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
+ - else
+ = f.submit 'Save changes', class: 'btn btn-save'
+ - if issuable.new_record?
+ - cancel_project = issuable.source_project
+ - else
+ - cancel_project = issuable.project
+ = link_to 'Cancel', [cancel_project.namespace.becomes(Namespace), cancel_project, issuable], class: 'btn btn-cancel'
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
new file mode 100644
index 0000000000..f356a25dbf
--- /dev/null
+++ b/app/views/projects/_md_preview.html.haml
@@ -0,0 +1,13 @@
+%ul.nav.nav-tabs
+ %li.active
+ = link_to '#md-write-holder', class: 'js-md-write-button' do
+ Write
+ %li
+ = link_to '#md-preview-holder', class: 'js-md-preview-button',
+ data: { url: markdown_preview_namespace_project_path(@project.namespace, @project) } do
+ Preview
+%div
+ .md-write-holder
+ = yield
+ .md-preview-holder.hide
+ .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml
index 9b73f06a5b..281a84a3d3 100644
--- a/app/views/projects/_settings_nav.html.haml
+++ b/app/views/projects/_settings_nav.html.haml
@@ -1,25 +1,31 @@
-%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20.project-settings-nav
+%ul.project-settings-nav.sidebar-subnav
= nav_link(path: 'projects#edit') do
- = link_to edit_project_path(@project), class: "stat-tab tab " do
- %i.icon-edit
- Project
- = nav_link(controller: [:team_members, :teams]) do
- = link_to project_team_index_path(@project), class: "team-tab tab" do
- %i.icon-group
- Members
+ = link_to edit_project_path(@project), title: 'Project', class: "stat-tab tab " do
+ %i.fa.fa-pencil-square-o
+ %span
+ Project
+ = nav_link(controller: [:project_members, :teams]) do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: "team-tab tab" do
+ %i.fa.fa-users
+ %span
+ Members
= nav_link(controller: :deploy_keys) do
- = link_to project_deploy_keys_path(@project) do
- %i.icon-key
- Deploy Keys
+ = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
+ %i.fa.fa-key
+ %span
+ Deploy Keys
= nav_link(controller: :hooks) do
- = link_to project_hooks_path(@project) do
- %i.icon-link
- Web Hooks
+ = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do
+ %i.fa.fa-link
+ %span
+ Web Hooks
= nav_link(controller: :services) do
- = link_to project_services_path(@project) do
- %i.icon-cogs
- Services
+ = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
+ %i.fa.fa-cogs
+ %span
+ Services
= nav_link(controller: :protected_branches) do
- = link_to project_protected_branches_path(@project) do
- %i.icon-lock
- Protected branches
+ = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
+ %i.fa.fa-lock
+ %span
+ Protected branches
diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml
index 5f34e66b3e..42c8e68522 100644
--- a/app/views/projects/_visibility_level.html.haml
+++ b/app/views/projects/_visibility_level.html.haml
@@ -7,8 +7,8 @@
- Gitlab::VisibilityLevel.values.each do |level|
.radio
- restricted = restricted_visibility_levels.include?(level)
- = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted
= label :project_visibility_level, level do
+ = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted
= visibility_level_icon(level)
.option-title
= visibility_level_label(level)
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
new file mode 100644
index 0000000000..cf1c55ecca
--- /dev/null
+++ b/app/views/projects/_zen.html.haml
@@ -0,0 +1,10 @@
+.zennable
+ %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
+ .zen-backdrop
+ - classes << ' js-gfm-input markdown-area'
+ = f.text_area attr, class: classes, placeholder: 'Leave a comment'
+ = link_to nil, class: 'zen-enter-link', tabindex: '-1' do
+ %i.fa.fa-expand
+ Edit in fullscreen
+ = link_to nil, class: 'zen-leave-link' do
+ %i.fa.fa-compress
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index cdca8b2e63..e6a859fea8 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -3,37 +3,33 @@
#tree-holder.tree-holder
.file-holder
.file-title
- %i.icon-file
- %span.file_name
+ %i.fa.fa-file
+ %strong
= @path
- %small= number_to_human_size @blob.size
- %span.options= render "projects/blob/actions"
- .file-content.blame
+ %small= number_to_human_size @blob.size
+ .file-actions
+ = render "projects/blob/actions"
+ .file-content.blame.highlight
%table
- - current_line = 1
- - @blame.each do |commit, lines|
+ - @blame.each do |commit, lines, since|
- commit = Commit.new(commit)
%tr
%td.blame-commit
%span.commit
- = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
+ = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "commit_short_id"
= commit_author_link(commit, avatar: true, size: 16)
- = link_to_gfm truncate(commit.title, length: 20), project_commit_path(@project, commit.id), class: "row_title"
+ = link_to_gfm truncate(commit.title, length: 20), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "row_title"
%td.lines.blame-numbers
%pre
- - if lines.empty?
- = current_line
- - current_line += 1
- - else
- - lines.each do |line|
- = current_line
- \
- - current_line += 1
+ - (since...(since + lines.count)).each do |i|
+ = i
+ \
%td.lines
- %pre
- :erb
- <% lines.each do |line| %>
- <%= line %>
- <% end %>
+ %pre{class: 'code highlight white'}
+ %code
+ :erb
+ <% lines.each do |line| %>
+ <%= highlight(@blob.name, line, true).html_safe %>
+ <% end %>
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index cabef3c19f..13f8271b97 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -1,19 +1,22 @@
.btn-group.tree-btn-group
- -# only show edit link for text files
- - if @blob.text?
- - if allowed_tree_edit?
- = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small"
- - else
- %span.btn.btn-small.disabled edit
- = link_to "raw", project_raw_path(@project, @id), class: "btn btn-small", target: "_blank"
+ = edit_blob_link(@project, @ref, @path)
+ = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
+ class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files
- if @blob.text?
- - if current_page? project_blame_path(@project, @id)
- = link_to "normal view", project_blob_path(@project, @id), class: "btn btn-small"
+ - if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
+ = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
+ class: 'btn btn-sm'
- else
- = link_to "blame", project_blame_path(@project, @id), class: "btn btn-small" unless @blob.empty?
- = link_to "history", project_commits_path(@project, @id), class: "btn btn-small"
+ = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),
+ class: 'btn btn-sm' unless @blob.empty?
+ = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
+ class: 'btn btn-sm'
+ - if @ref != @commit.sha
+ = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
+ tree_join(@commit.sha, @path)), class: 'btn btn-sm'
- if allowed_tree_edit?
- = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do
- remove
+ = button_tag class: 'remove-blob btn btn-sm btn-remove',
+ 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do
+ Remove
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index be785daced..65c3ab10e0 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -1,32 +1,34 @@
%ul.breadcrumb.repo-breadcrumb
%li
- %i.icon-angle-right
- = link_to project_tree_path(@project, @ref) do
+ %i.fa.fa-angle-right
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
- tree_breadcrumbs(@tree, 6) do |title, path|
%li
- if path
- if path.end_with?(@path)
- = link_to project_blob_path(@project, path) do
+ = link_to namespace_project_blob_path(@project.namespace, @project, path) do
%strong
= truncate(title, length: 40)
- else
- = link_to truncate(title, length: 40), project_tree_path(@project, path)
+ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to title, '#'
-%ul.blob-commit-info.bs-callout.bs-callout-info.hidden-xs
- - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
+%ul.blob-commit-info.well.hidden-xs
+ - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project
%div#tree-content-holder.tree-content-holder
%article.file-holder
- .file-title.clearfix
- %i.icon-file
- %span.file_name
+ .file-title
+ = blob_icon blob.mode, blob.name
+ %strong
= blob.name
- %small= number_to_human_size blob.size
- %span.options.hidden-xs= render "actions"
+ %small
+ = number_to_human_size(blob.size)
+ .file-actions.hidden-xs
+ = render "actions"
- if blob.text?
= render "text", blob: blob
- elsif blob.image?
diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml
index cdbfee7cc6..f2c5e95ecf 100644
--- a/app/views/projects/blob/_download.html.haml
+++ b/app/views/projects/blob/_download.html.haml
@@ -1,7 +1,7 @@
.file-content.blob_file.blob-no-preview
.center
- = link_to project_raw_path(@project, @id) do
+ = link_to namespace_project_raw_path(@project.namespace, @project, @id) do
%h1.light
- %i.icon-download-alt
+ %i.fa.fa-download
%h4
Download (#{number_to_human_size blob.size})
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
new file mode 100644
index 0000000000..96f188e4aa
--- /dev/null
+++ b/app/views/projects/blob/_editor.html.haml
@@ -0,0 +1,25 @@
+.file-holder.file
+ .file-title
+ .editor-ref
+ %i.fa.fa-code-fork
+ = ref
+ %span.editor-file-name
+ - if @path
+ %span.monospace
+ = @path
+
+ - if current_action?(:new) || current_action?(:create)
+ \/
+ = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
+ required: true, class: 'form-control new-file-name'
+ .pull-right
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+
+ .file-content.code
+ %pre.js-edit-mode-pane#editor
+ = params[:content] || local_assigns[:blob_data]
+ - if local_assigns[:path]
+ .js-edit-mode-pane#preview.hide
+ .center
+ %h2
+ %i.icon-spinner.icon-spin
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index 692248dd23..09559a4967 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -9,15 +9,14 @@
%strong= @ref
.modal-body
- = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal' do
- .form-group.commit_message-group
- = label_tag 'commit_message', class: "control-label" do
- Commit message
- .col-sm-10
- = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message',
- params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3, class: 'form-control')}
+ = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal' do
+ = render 'shared/commit_message_container', params: params,
+ placeholder: 'Removed this file because...'
.form-group
.col-sm-2
.col-sm-10
- = submit_tag 'Remove file', class: 'btn btn-remove'
+ = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+:javascript
+ disableButtonIfEmptyField('#commit_message', '.btn-remove-file')
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 7cbea7c3eb..f6bd62f239 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -8,6 +8,6 @@
- else
.file-content.code
- unless blob.empty?
- = render 'shared/file_hljs', blob: blob
+ = render 'shared/file_highlight', blob: blob
- else
.nothing-here-block Empty file
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index cfb91d6568..5c79d0ef11 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -1,7 +1,7 @@
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder{ id: @form.since }
- = render "projects/commits/diffs/match_line", {line: @match_line,
+ = render "projects/diffs/match_line", {line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false}
- @lines.each_with_index do |line, index|
@@ -15,5 +15,5 @@
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to }
- = render "projects/commits/diffs/match_line", {line: @match_line,
+ = render "projects/diffs/match_line", {line: @match_line,
line_old: @form.to, line_new: @form.to, bottom: true}
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
new file mode 100644
index 0000000000..1f61a0b940
--- /dev/null
+++ b/app/views/projects/blob/edit.html.haml
@@ -0,0 +1,31 @@
+.file-editor
+ %ul.nav.nav-tabs.js-edit-mode
+ %li.active
+ = link_to '#editor' do
+ %i.fa.fa-edit
+ Edit file
+
+ %li
+ = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
+ %i.fa.fa-eye
+ = editing_preview_title(@blob.name)
+
+ = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: "form-horizontal") do
+ = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
+ = render 'shared/commit_message_container', params: params,
+ placeholder: "Update #{@blob.name}"
+
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
+
+ = hidden_field_tag 'last_commit', @last_commit
+ = hidden_field_tag 'content', '', id: "file-content"
+ = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
+ = render 'projects/commit_button', ref: @ref,
+ cancel_path: @after_edit_path
+
+:javascript
+ blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
new file mode 100644
index 0000000000..d78a01f642
--- /dev/null
+++ b/app/views/projects/blob/new.html.haml
@@ -0,0 +1,19 @@
+%h3.page-title New file
+.file-editor
+ = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file') do
+ = render 'projects/blob/editor', ref: @ref
+ = render 'shared/commit_message_container', params: params,
+ placeholder: 'Add new file'
+
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
+
+ = hidden_field_tag 'content', '', id: 'file-content'
+ = render 'projects/commit_button', ref: @ref,
+ cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
+
+:javascript
+ blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
new file mode 100644
index 0000000000..e7c3460ad7
--- /dev/null
+++ b/app/views/projects/blob/preview.html.haml
@@ -0,0 +1,25 @@
+.diff-file
+ .diff-content
+ - if gitlab_markdown?(@blob.name)
+ .file-content.wiki
+ = preserve do
+ = markdown(@content)
+ - elsif markup?(@blob.name)
+ .file-content.wiki
+ = raw render_markup(@blob.name, @content)
+ - else
+ .file-content.code
+ - unless @diff_lines.empty?
+ %table.text-file
+ - @diff_lines.each do |line|
+ %tr.line_holder{ class: "#{line.type}" }
+ - if line.type == "match"
+ %td.old_line= "..."
+ %td.new_line= "..."
+ %td.line_content.matched= line.text
+ - else
+ %td.old_line
+ %td.new_line
+ %td.line_content{class: "#{line.type}"}= raw diff_line_content(line.text)
+ - else
+ .nothing-here-block No changes.
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 54a7b934dd..4e7415be4a 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,25 +1,25 @@
- commit = @repository.commit(branch.target)
%li(class="js-branch-#{branch.name}")
%h4
- = link_to project_tree_path(@project, branch.name) do
- %strong= truncate(branch.name, length: 60)
+ = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
+ %strong.str-truncated= branch.name
- if branch.name == @repository.root_ref
%span.label.label-info default
- if @project.protected_branch? branch.name
%span.label.label-success
- %i.icon-lock
+ %i.fa.fa-lock
protected
.pull-right
- if can?(current_user, :download_code, @project)
- = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-small'
+ = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-xs'
- if branch.name != @repository.root_ref
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-small', method: :post, title: "Compare" do
- %i.icon-copy
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-xs', method: :post, title: "Compare" do
+ %i.fa.fa-files-o
Compare
- if can_remove_branch?(@project, branch.name)
- = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
- %i.icon-trash
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
+ %i.fa.fa-trash-o
- if commit
%ul.list-unstyled
diff --git a/app/views/projects/branches/destroy.js.haml b/app/views/projects/branches/destroy.js.haml
index ec1661c0aa..882a4d0c5e 100644
--- a/app/views/projects/branches/destroy.js.haml
+++ b/app/views/projects/branches/destroy.js.haml
@@ -1,3 +1 @@
-:plain
- $(".js-branch-#{@branch_name}").remove();
- $('.js-totalbranch-count').html("#{@repository.branches.size}")
+$('.js-totalbranch-count').html("#{@repository.branches.size}")
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 8bc4a8f7e9..a313ffcf27 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -3,12 +3,12 @@
Branches
.pull-right
- if can? current_user, :push_code, @project
- = link_to new_project_branch_path(@project), class: 'btn btn-create' do
- %i.icon-add-sign
+ = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
+ %i.fa.fa-add-sign
New branch
.dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= @sort.humanize
@@ -17,12 +17,12 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to project_branches_path(sort: nil) do
+ = link_to namespace_project_branches_path(sort: nil) do
Name
- = link_to project_branches_path(sort: 'recently_updated') do
- Recently updated
- = link_to project_branches_path(sort: 'last_updated') do
- Last updated
+ = link_to namespace_project_branches_path(sort: 'recently_updated') do
+ = sort_title_recently_updated
+ = link_to namespace_project_branches_path(sort: 'last_updated') do
+ = sort_title_oldest_updated
%hr
- unless @branches.empty?
%ul.bordered-list.top-list.all-branches
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 5da2ede293..e5fcb98c68 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -1,20 +1,25 @@
+- if @error
+ .alert.alert-danger
+ %button{ type: "button", class: "close", "data-dismiss" => "alert"} ×
+ = @error
%h3.page-title
- %i.icon-code-fork
+ %i.fa.fa-code-fork
New branch
-= form_tag project_branches_path, method: :post, class: "form-horizontal" do
+= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal" do
.form-group
= label_tag :branch_name, 'Name for new branch', class: 'control-label'
.col-sm-10
- = text_field_tag :branch_name, nil, placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control'
+ = text_field_tag :branch_name, params[:branch_name], placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
- = text_field_tag :ref, nil, placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
+ = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
.form-actions
- = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
+ = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
+ = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
+ disableButtonIfAnyEmptyField($("#new-branch-form"), ".form-control", ".btn-create");
var availableTags = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 2bc9048b2a..3f645b8139 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -2,23 +2,24 @@
%div
- if @notes_count > 0
%span.btn.disabled.btn-grouped
- %i.icon-comment
+ %i.fa.fa-comment
= @notes_count
.pull-left.btn-group
%a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
- %i.icon-download-alt
+ %i.fa.fa-download
Download as
%span.caret
%ul.dropdown-menu
- %li= link_to "Email Patches", project_commit_path(@project, @commit, format: :patch)
- %li= link_to "Plain Diff", project_commit_path(@project, @commit, format: :diff)
- = link_to project_tree_path(@project, @commit), class: "btn btn-primary btn-grouped" do
+ - unless @commit.parents.length > 1
+ %li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
+ %li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
+ = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do
%span Browse Code »
%div
%p
%span.light Commit
- = link_to @commit.id, project_commit_path(@project, @commit)
+ = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit)
.commit-info-row
%span.light Authored by
%strong
@@ -35,20 +36,10 @@
.commit-info-row
%span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent|
- = link_to parent.id[0...10], project_commit_path(@project, parent)
+ = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent)
-- if @branches.any?
- .commit-info-row
- %span.cgray
- Exists in
- %span
- - branch = commit_default_branch(@project, @branches)
- = link_to(branch, project_tree_path(@project, branch))
- - if @branches.any?
- and in
- = link_to("#{pluralize(@branches.count, "other branch")}", "#", class: "js-details-expand")
- %span.js-details-content.hide
- = commit_branches_links(@project, @branches)
+.commit-info-row.branches
+ %i.fa.fa-spinner.fa-spin
.commit-box
%h3.commit-title
@@ -56,3 +47,6 @@
- if @commit.description.present?
%pre.commit-description
= preserve(gfm(escape_once(@commit.description)))
+
+:coffeescript
+ $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}")
diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml
new file mode 100644
index 0000000000..82aac1fbd1
--- /dev/null
+++ b/app/views/projects/commit/branches.html.haml
@@ -0,0 +1,16 @@
+- if @branches.any?
+ %span
+ - branch = commit_default_branch(@project, @branches)
+ = link_to(namespace_project_tree_path(@project.namespace, @project, branch)) do
+ %span.label.label-gray
+ %i.fa.fa-code-fork
+ = branch
+ - if @branches.any? || @tags.any?
+ = link_to("#", class: "js-details-expand") do
+ %span.label.label-gray
+ \...
+ %span.js-details-content.hide
+ - if @branches.any?
+ = commit_branches_links(@project, @branches)
+ - if @tags.any?
+ = commit_tags_links(@project, @tags)
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 0a15aef6cb..fc721067ed 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,3 +1,3 @@
= render "commit_box"
-= render "projects/commits/diffs", diffs: @diffs, project: @project
+= render "projects/diffs/diffs", diffs: @diffs, project: @project
= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 8e73663939..c6026f9680 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,24 +1,24 @@
%li.commit.js-toggle-container
.commit-row-title
- = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
-
- %span.str-truncated
- = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
+ %strong.str-truncated
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description?
%a.text-expander.js-toggle-button ...
- = link_to_browse_code(project, commit)
+ .pull-right
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.notes_count
- if @note_counts
- note_count = @note_counts.fetch(commit.id, 0)
- else
- notes = project.notes.for_commit_id(commit.id)
- - note_count = notes.count
+ - note_count = notes.user.count
- if note_count > 0
- %span.label.label-gray
- %i.icon-comment= note_count
+ %span.light
+ %i.fa.fa-comments
+ = note_count
- if commit.description?
.commit-row-description.js-toggle-content
@@ -26,6 +26,8 @@
= preserve(gfm(escape_once(commit.description)))
.commit-row-info
- = commit_author_link(commit, avatar: true, size: 16)
+ = commit_author_link(commit, avatar: true, size: 24)
+ authored
.committed_ago
#{time_ago_with_tooltip(commit.committed_date)}
+ = link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml
new file mode 100644
index 0000000000..2ee7d73bd2
--- /dev/null
+++ b/app/views/projects/commits/_commit_list.html.haml
@@ -0,0 +1,11 @@
+%div.panel.panel-default
+ .panel-heading
+ Commits (#{@commits.count})
+ - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ %ul.well-list
+ - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit|
+ = render "projects/commits/inline_commit", commit: commit, project: @project
+ %li.warning-row.unstyled
+ other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
+ - else
+ %ul.well-list= render Commit.decorate(@commits), project: @project
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 77f795f23f..0cd9ce1f37 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -1,11 +1,15 @@
+- unless defined?(project)
+ - project = @project
+
- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
.row.commits-row
- .col-md-2
- %h4
- %i.icon-calendar
+ .col-md-2.hidden-xs.hidden-sm
+ %h5.commits-row-date
+ %i.fa.fa-calendar
%span= day.stamp("28 Aug, 2010")
- %p= pluralize(commits.count, 'commit')
- .col-md-10
+ .light
+ = pluralize(commits.count, 'commit')
+ .col-md-10.col-sm-12
%ul.bordered-list
- = render commits, project: @project
+ = render commits, project: project
%hr.lists-separator
diff --git a/app/views/projects/commits/_diff_file.html.haml b/app/views/projects/commits/_diff_file.html.haml
deleted file mode 100644
index 6e6107c884..0000000000
--- a/app/views/projects/commits/_diff_file.html.haml
+++ /dev/null
@@ -1,44 +0,0 @@
-- file = project.repository.blob_for_diff(@commit, diff)
-- return unless file
-- blob_diff_path = diff_project_blob_path(project,
- tree_join(@commit.id, diff.new_path))
-.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }}
- .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"}
- - if diff.deleted_file
- %span= diff.old_path
-
- .diff-btn-group
- - if @commit.parent_ids.present?
- = view_file_btn(@commit.parent_id, diff, project)
- - else
- %span= diff.new_path
- - if diff_file_mode_changed?(diff)
- %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
-
- .diff-btn-group
- = link_to "#", class: "js-toggle-diff-comments btn btn-small" do
- %i.icon-chevron-down
- Diff comments
-
-
- - if @merge_request && @merge_request.source_project
- = link_to project_edit_tree_path(@merge_request.source_project, tree_join(@merge_request.source_branch, diff.new_path), from_merge_request_id: @merge_request.id), { class: 'btn btn-small' } do
- Edit
-
-
- = view_file_btn(@commit.id, diff, project)
-
- .diff-content
- -# Skipp all non non-supported blobs
- - return unless file.respond_to?('text?')
- - if file.text?
- - if params[:view] == 'parallel'
- = render "projects/commits/parallel_view", diff: diff, project: project, file: file, index: i
- - else
- = render "projects/commits/text_file", diff: diff, index: i
- - elsif file.image?
- - old_file = project.repository.prev_blob_for_diff(@commit, diff)
- = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i
- - else
- .nothing-here-block No preview for this file type
-
diff --git a/app/views/projects/commits/_diff_warning.html.haml b/app/views/projects/commits/_diff_warning.html.haml
deleted file mode 100644
index 05d516efa1..0000000000
--- a/app/views/projects/commits/_diff_warning.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-.bs-callout.bs-callout-warning
- %h4
- Too many changes.
- .pull-right
- - unless diff_hard_limit_enabled?
- = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-small btn-warning"
-
- - if current_controller?(:commit) or current_controller?(:merge_requests)
- - if current_controller?(:commit)
- = link_to "Plain diff", project_commit_path(@project, @commit, format: :diff), class: "btn btn-warning btn-small"
- = link_to "Email patch", project_commit_path(@project, @commit, format: :patch), class: "btn btn-warning btn-small"
- - elsif @merge_request && @merge_request.persisted?
- = link_to "Plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "btn btn-warning btn-small"
- = link_to "Email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "btn btn-warning btn-small"
- %p
- To preserve performance only
- %strong #{safe_diff_files(diffs).size} of #{diffs.size}
- files displayed.
-
diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml
deleted file mode 100644
index 64d6a2f09c..0000000000
--- a/app/views/projects/commits/_diffs.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-.row
- .col-md-8
- = render 'projects/commits/diff_stats', diffs: diffs
- .col-md-4
- %ul.nav.nav-tabs
- %li.pull-right{class: params[:view] == 'parallel' ? 'active' : ''}
- = link_to "Side-by-side Diff", url_for(view: 'parallel'), {id: "commit-diff-viewtype"}
- %li.pull-right{class: params[:view] != 'parallel' ? 'active' : ''}
- = link_to "Inline Diff", url_for(view: 'inline'), {id: "commit-diff-viewtype"}
-
-- if show_diff_size_warninig?(diffs)
- = render 'projects/commits/diff_warning', diffs: diffs
-
-.files
- - safe_diff_files(diffs).each_with_index do |diff, i|
- = render 'projects/commits/diff_file', diff: diff, i: i, project: project
-
-- if @diff_timeout
- .alert.alert-danger
- %h4
- Failed to collect changes
- %p
- Maybe diff is really big and operation failed with timeout. Try to get diff localy
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index b636e8ffe1..a714f5f79e 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,19 +1,17 @@
%ul.nav.nav-tabs
= nav_link(controller: [:commit, :commits]) do
- = link_to 'Commits', project_commits_path(@project, @repository.root_ref)
+ = link_to namespace_project_commits_path(@project.namespace, @project, @repository.root_ref) do
+ Commits
+ %span.badge= number_with_precision(@repository.commit_count, precision: 0, delimiter: ',')
= nav_link(controller: :compare) do
- = link_to 'Compare', project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref)
+ = link_to 'Compare', namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref)
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project) do
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
Branches
%span.badge.js-totalbranch-count= @repository.branches.size
= nav_link(controller: :tags) do
- = link_to project_tags_path(@project) do
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
Tags
- %span.badge= @repository.tags.length
-
- = nav_link(controller: :repositories, action: :stats) do
- = link_to stats_project_repository_path(@project) do
- Stats
+ %span.badge.js-totaltags-count= @repository.tags.length
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index b36369b428..c03bc3f9df 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -1,8 +1,8 @@
%li.commit.inline-commit
.commit-row-title
- = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
%span.str-truncated
- = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
.pull-right
#{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/_parallel_view.html.haml b/app/views/projects/commits/_parallel_view.html.haml
deleted file mode 100644
index 80f5be98f2..0000000000
--- a/app/views/projects/commits/_parallel_view.html.haml
+++ /dev/null
@@ -1,38 +0,0 @@
-/ Side-by-side diff view
-- old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file)
-- num_lines = old_lines.length
-
-%div.text-file
- %table
- - num_lines.times do |index|
- - new_line = new_lines[index]
- - old_line = old_lines[index]
- %tr.line_holder.parallel
- -# For old line
- - if old_line.type == :file_created
- %td.old_line= old_line.num
- %td.line_content.parallel= "File was created"
- - elsif old_line.type == :deleted
- %td.old_line.old= old_line.num
- %td.line_content{class: "parallel noteable_line old #{old_line.code}", "line_code" => old_line.code}= old_line.content
- - else old_line.type == :no_change
- %td.old_line= old_line.num
- %td.line_content.parallel= old_line.content
-
- -# For new line
- - if new_line.type == :file_deleted
- %td.new_line= new_line.num
- %td.line_content.parallel= "File was deleted"
- - elsif new_line.type == :added
- %td.new_line.new= new_line.num
- %td.line_content{class: "parallel noteable_line new #{new_line.code}", "line_code" => new_line.code}= new_line.content
- - else new_line.type == :no_change
- %td.new_line= new_line.num
- %td.line_content.parallel= new_line.content
-
- - if @reply_allowed
- - comments1 = @line_notes.select { |n| n.line_code == old_line.code }.sort_by(&:created_at)
- - comments2 = @line_notes.select { |n| n.line_code == new_line.code }.sort_by(&:created_at)
- - unless comments1.empty? and comments2.empty?
- = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2
-
diff --git a/app/views/projects/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml
deleted file mode 100644
index 756481c1b2..0000000000
--- a/app/views/projects/commits/_text_file.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-- too_big = diff.diff.lines.count > Commit::DIFF_SAFE_LINES
-- if too_big
- %a.supp_diff_link Changes suppressed. Click to show
-
-%table.text-file{class: "#{'hide' if too_big}"}
- - last_line = 0
- - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line|
- - last_line = line_new
- %tr.line_holder{ id: line_code, class: "#{type}" }
- - if type == "match"
- = render "projects/commits/diffs/match_line", {line: line,
- line_old: line_old, line_new: line_new, bottom: false}
- - else
- %td.old_line
- = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code
- - if @comments_allowed
- = link_to_new_diff_note(line_code)
- %td.new_line{data: {linenumber: line_new}}
- = link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code
- %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
-
- - if @reply_allowed
- - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
- - unless comments.empty?
- = render "projects/notes/diff_notes_with_reply", notes: comments, line: line
-
- - if last_line > 0
- = render "projects/commits/diffs/match_line", {line: "",
- line_old: last_line, line_new: last_line, bottom: true}
-
-- if diff.diff.blank? && diff_file_mode_changed?(diff)
- .file-mode-changed
- File mode changed
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 32c82edb24..9211de72b1 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -1,15 +1,15 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Recent commits to #{@project.name}:#{@ref}"
- xml.link :href => project_commits_url(@project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => project_commits_url(@project, @ref), :rel => "alternate", :type => "text/html"
- xml.id project_commits_url(@project, @ref)
+ xml.link :href => namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml"
+ xml.link :href => namespace_project_commits_url(@project.namespace, @project, @ref), :rel => "alternate", :type => "text/html"
+ xml.id namespace_project_commits_url(@project.namespace, @project, @ref)
xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any?
@commits.each do |commit|
xml.entry do
- xml.id project_commit_url(@project, :id => commit.id)
- xml.link :href => project_commit_url(@project, :id => commit.id)
+ xml.id namespace_project_commit_url(@project.namespace, @project, :id => commit.id)
+ xml.link :href => namespace_project_commit_url(@project.namespace, @project, :id => commit.id)
xml.title truncate(commit.title, :length => 80)
xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(commit.author_email)
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 482a845558..7ea855e1a4 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -5,17 +5,15 @@
- if current_user && current_user.private_token
.commits-feed-holder.hidden-xs.hidden-sm
- = link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'btn' do
- %i.icon-rss
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'btn' do
+ %i.fa.fa-rss
Commits feed
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
- %li.active
- commits
%div{id: dom_id(@project)}
- #commits-list= render "commits"
+ #commits-list= render "commits", project: @project
.clear
= spinner
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index da6157cf1b..dfb1dded9e 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,4 +1,4 @@
-= form_tag project_compare_index_path(@project), method: :post, class: 'form-inline' do
+= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline' do
.clearfix.append-bottom-20
- if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
@@ -12,7 +12,7 @@
%span.input-group-addon to
= text_field_tag :to, params[:to], class: "form-control"
- = submit_tag "Compare", class: "btn btn-create commits-compare-btn"
+ = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if compare_to_mr_button?
= link_to compare_mr_path, class: 'prepend-left-10 btn' do
%strong Make a merge request
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index aa79d08509..214b5bd337 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -6,20 +6,8 @@
= render "form"
- if @commits.present?
- %div.panel.panel-default
- .panel-heading
- Commits (#{@commits.count})
- - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- %ul.well-list
- - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit|
- = render "projects/commits/inline_commit", commit: commit, project: @project
- %li.warning-row.unstyled
- other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
- - else
- %ul.well-list= render Commit.decorate(@commits), project: @project
-
- = render "projects/commits/diffs", diffs: @diffs, project: @project
-
+ = render "projects/commits/commit_list"
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project
- else
.light-well
.center
diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml
deleted file mode 100644
index 89710d3a09..0000000000
--- a/app/views/projects/create.js.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- if @project.saved?
- - if @project.import?
- :plain
- location.href = "#{import_project_path(@project)}";
- - else
- :plain
- location.href = "#{project_path(@project)}";
-- else
- :plain
- $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
- $('.project-submit').enable();
- $('.save-project-loader').hide();
- $('.project-edit-container').show();
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index 2b4f36fb4b..c577dfa8d5 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -1,24 +1,36 @@
%li
.pull-right
- if @available_keys.include?(deploy_key)
- = link_to enable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
- %i.icon-plus
+ = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
+ %i.fa.fa-plus
Enable
- else
- - if deploy_key.projects.count > 1
- = link_to disable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
- %i.icon-off
- Disable
+ - if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned?
+ = link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right"
- else
- = link_to 'Remove', project_deploy_key_path(@project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
+ = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
+ %i.fa.fa-power-off
+ Disable
-
- = link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do
- %i.icon-key
+ - if project = project_for_deploy_key(deploy_key)
+ = link_to namespace_project_deploy_key_path(project.namespace, project, deploy_key) do
+ %i.fa.fa-key
+ %strong= deploy_key.title
+ - else
+ %i.fa.fa-key
%strong= deploy_key.title
+
%p.light.prepend-top-10
- - deploy_key.projects.map(&:name_with_namespace).each do |project_name|
- %span.label.label-gray.deploy-project-label= project_name
+ - if deploy_key.public?
+ %span.label.label-info.deploy-project-label
+ Public deploy key
+
+ - deploy_key.projects.each do |project|
+ - if can?(current_user, :read_project, project)
+ %span.label.label-gray.deploy-project-label
+ = link_to namespace_project_path(project.namespace, project) do
+ = project.name_with_namespace
+
%small.pull-right
Created #{time_ago_with_tooltip(deploy_key.created_at)}
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 162ef05b36..91675b3738 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,5 +1,5 @@
%div
- = form_for [@project, @key], url: project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
-if @key.errors.any?
.alert.alert-danger
%ul
@@ -19,5 +19,5 @@
.form-actions
= f.submit 'Create', class: "btn-create btn"
- = link_to "Cancel", project_deploy_keys_path(@project), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index f50aeba337..472a13a852 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -1,8 +1,8 @@
%h3.page-title
Deploy keys allow read-only access to the repository
- = link_to new_project_deploy_key_path(@project), class: "btn btn-new pull-right", title: "New Deploy Key" do
- %i.icon-plus
+ = link_to new_namespace_project_deploy_key_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Deploy Key" do
+ %i.fa.fa-plus
New Deploy Key
%p.light
@@ -20,13 +20,22 @@
= render @enabled_keys
- if @enabled_keys.blank?
.light-well
- .nothing-here-block Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one
+ .nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one
.col-md-6.available-keys
- %h5
- %strong Deploy keys
- from projects available to you
- %ul.bordered-list
- = render @available_keys
- - if @available_keys.blank?
- .light-well
- .nothing-here-block Deploy keys from projects you have access to will be displayed here
+ - # If there are available public deploy keys but no available project deploy keys, only public deploy keys are shown.
+ - if @available_project_keys.any? || @available_public_keys.blank?
+ %h5
+ %strong Deploy keys
+ from projects you have access to
+ %ul.bordered-list
+ = render @available_project_keys
+ - if @available_project_keys.blank?
+ .light-well
+ .nothing-here-block Deploy keys from projects you have access to will be displayed here
+
+ - if @available_public_keys.any?
+ %h5
+ %strong Public deploy keys
+ available to any project
+ %ul.bordered-list
+ = render @available_public_keys
diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml
index c66e6bc69c..405b5bcd0d 100644
--- a/app/views/projects/deploy_keys/show.html.haml
+++ b/app/views/projects/deploy_keys/show.html.haml
@@ -5,9 +5,9 @@
created on
= @key.created_at.stamp("Aug 21, 2011")
.back-link
- = link_to project_deploy_keys_path(@project) do
+ = link_to namespace_project_deploy_keys_path(@project.namespace, @project) do
← To keys list
%hr
%pre= @key.key
.pull-right
- = link_to 'Remove', project_deploy_key_path(@project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key"
+ = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key"
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
new file mode 100644
index 0000000000..b49aee504f
--- /dev/null
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -0,0 +1,23 @@
+.prepend-top-20.append-bottom-20
+ .pull-right
+ .btn-group
+ = inline_diff_btn
+ = parallel_diff_btn
+ = render 'projects/diffs/stats', diffs: diffs
+
+- if show_diff_size_warning?(diffs)
+ = render 'projects/diffs/warning', diffs: diffs
+
+.files
+ - safe_diff_files(diffs).each_with_index do |diff_file, index|
+ = render 'projects/diffs/file', diff_file: diff_file, i: index, project: project
+
+- if @diff_timeout
+ .alert.alert-danger
+ %h4
+ Failed to collect changes
+ %p
+ Maybe diff is really big and operation failed with timeout. Try to get diff locally
+
+:coffeescript
+ $('.files .diff-header').stick_in_parent(offset_top: $('.navbar').height())
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
new file mode 100644
index 0000000000..672a663532
--- /dev/null
+++ b/app/views/projects/diffs/_file.html.haml
@@ -0,0 +1,50 @@
+- blob = project.repository.blob_for_diff(@commit, diff_file.diff)
+- return unless blob
+- blob_diff_path = namespace_project_blob_diff_path(project.namespace, project, tree_join(@commit.id, diff_file.file_path))
+.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }}
+ .diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
+ - if diff_file.deleted_file
+ %span="#{diff_file.old_path} deleted"
+
+ .diff-btn-group
+ - if @commit.parent_ids.present?
+ = view_file_btn(@commit.parent_id, diff_file, project)
+ - elsif diff_file.diff.submodule?
+ - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
+ = submodule_link(submodule_item, @commit.id)
+ - else
+ %span
+ - if diff_file.renamed_file
+ = "#{diff_file.old_path} renamed to #{diff_file.new_path}"
+ - else
+ = diff_file.new_path
+ - if diff_file.mode_changed?
+ %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
+
+ .diff-btn-group
+ - if blob.text?
+ = link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
+ %i.fa.fa-comments
+
+
+ - if @merge_request && @merge_request.source_project
+ = edit_blob_link(@merge_request.source_project,
+ @merge_request.source_branch, diff_file.new_path,
+ after: ' ', from_merge_request_id: @merge_request.id)
+
+ = view_file_btn(@commit.id, diff_file, project)
+
+ .diff-content.diff-wrap-lines
+ -# Skipp all non non-supported blobs
+ - return unless blob.respond_to?('text?')
+ - if blob.text?
+ - if params[:view] == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
+ - else
+ = render "projects/diffs/text_file", diff_file: diff_file, index: i
+ - elsif blob.image?
+ - old_file = project.repository.prev_blob_for_diff(@commit, diff_file)
+ = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
+ - else
+ .nothing-here-block No preview for this file type
+
diff --git a/app/views/projects/commits/_image.html.haml b/app/views/projects/diffs/_image.html.haml
similarity index 88%
rename from app/views/projects/commits/_image.html.haml
rename to app/views/projects/diffs/_image.html.haml
index 6d9ef5964d..058b71b21f 100644
--- a/app/views/projects/commits/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -1,3 +1,4 @@
+- diff = diff_file.diff
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
%span.wrap
@@ -9,7 +10,7 @@
%div.two-up.view
%span.wrap
.frame.deleted
- %a{href: project_blob_path(@project, tree_join(@commit.parent_id, diff.old_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))}
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
@@ -21,7 +22,7 @@
%span.meta-height
%span.wrap
.frame.added
- %a{href: project_blob_path(@project, tree_join(@commit.id, diff.new_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}"
diff --git a/app/views/projects/commits/diffs/_match_line.html.haml b/app/views/projects/diffs/_match_line.html.haml
similarity index 100%
rename from app/views/projects/commits/diffs/_match_line.html.haml
rename to app/views/projects/diffs/_match_line.html.haml
diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml
new file mode 100644
index 0000000000..815df16aa4
--- /dev/null
+++ b/app/views/projects/diffs/_match_line_parallel.html.haml
@@ -0,0 +1,4 @@
+%td.old_line
+ %td.line_content.parallel.matched= line
+%td.new_line
+ %td.line_content.parallel.matched= line
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
new file mode 100644
index 0000000000..75f3a80f0d
--- /dev/null
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -0,0 +1,41 @@
+/ Side-by-side diff view
+%div.text-file.diff-wrap-lines
+ %table
+ - parallel_diff(diff_file, index).each do |line|
+ - type_left = line[0]
+ - line_number_left = line[1]
+ - line_content_left = line[2]
+ - line_code_left = line[3]
+ - type_right = line[4]
+ - line_number_right = line[5]
+ - line_content_right = line[6]
+ - line_code_right = line[7]
+
+ %tr.line_holder.parallel
+ - if type_left == 'match'
+ = render "projects/diffs/match_line_parallel", { line: line_content_left,
+ line_old: line_number_left, line_new: line_number_right }
+ - elsif type_left == 'old' || type_left.nil?
+ %td.old_line{id: line_code_left, class: "#{type_left}"}
+ = link_to raw(line_number_left), "##{line_code_left}", id: line_code_left
+ %td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }= raw line_content_left
+
+ - if type_right == 'new'
+ - new_line_class = 'new'
+ - new_line_code = line_code_right
+ - else
+ - new_line_class = nil
+ - new_line_code = line_code_left
+
+ %td.new_line{id: new_line_code, class: "#{new_line_class}", data: { linenumber: line_number_right }}
+ = link_to raw(line_number_right), "##{new_line_code}", id: new_line_code
+ %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= raw line_content_right
+
+ - if @reply_allowed
+ - comments_left, comments_right = organize_comments(type_left, type_right, line_code_left, line_code_right)
+ - if comments_left.present? || comments_right.present?
+ = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments_left, notes2: comments_right
+
+- if diff_file.diff.diff.blank? && diff_file.mode_changed?
+ .file-mode-changed
+ File mode changed
diff --git a/app/views/projects/commits/_diff_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
similarity index 74%
rename from app/views/projects/commits/_diff_stats.html.haml
rename to app/views/projects/diffs/_stats.html.haml
index 846a1ee10e..1625930615 100644
--- a/app/views/projects/commits/_diff_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -1,17 +1,14 @@
.js-toggle-container
.commit-stat-summary
Showing
- %strong.cdark #{pluralize(diffs.count, "changed file")}
+ = link_to '#', class: 'js-toggle-button' do
+ %strong #{pluralize(diffs.count, "changed file")}
- if current_controller?(:commit)
- unless @commit.has_zero_stats?
with
%strong.cgreen #{@commit.stats.additions} additions
and
%strong.cred #{@commit.stats.deletions} deletions
-
- = link_to '#', class: 'btn btn-small js-toggle-button' do
- Show diff stats
- %i.icon-chevron-down
.file-stats.js-toggle-content.hide
%ul.bordered-list
- diffs.each_with_index do |diff, i|
@@ -19,23 +16,23 @@
- if diff.deleted_file
%span.deleted-file
%a{href: "#diff-#{i}"}
- %i.icon-minus
+ %i.fa.fa-minus
= diff.old_path
- elsif diff.renamed_file
%span.renamed-file
%a{href: "#diff-#{i}"}
- %i.icon-minus
+ %i.fa.fa-minus
= diff.old_path
- = "->"
+ →
= diff.new_path
- elsif diff.new_file
%span.new-file
%a{href: "#diff-#{i}"}
- %i.icon-plus
+ %i.fa.fa-plus
= diff.new_path
- else
%span.edit-file
%a{href: "#diff-#{i}"}
- %i.icon-adjust
+ %i.fa.fa-adjust
= diff.new_path
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
new file mode 100644
index 0000000000..e6dfbfd651
--- /dev/null
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -0,0 +1,36 @@
+- too_big = diff_file.diff_lines.count > Commit::DIFF_SAFE_LINES
+- if too_big
+ %a.supp_diff_link Changes suppressed. Click to show
+
+%table.text-file{class: "#{'hide' if too_big}"}
+ - last_line = 0
+ - diff_file.diff_lines.each_with_index do |line, index|
+ - type = line.type
+ - last_line = line.new_pos
+ - line_code = generate_line_code(diff_file.file_path, line)
+ - line_old = line.old_pos
+ %tr.line_holder{ id: line_code, class: "#{type}" }
+ - if type == "match"
+ = render "projects/diffs/match_line", {line: line.text,
+ line_old: line_old, line_new: line.new_pos, bottom: false}
+ - else
+ %td.old_line
+ = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code
+ - if @comments_allowed && can?(current_user, :write_note, @project)
+ = link_to_new_diff_note(line_code)
+ %td.new_line{data: {linenumber: line.new_pos}}
+ = link_to raw(type == "old" ? " " : line.new_pos) , "##{line_code}", id: line_code
+ %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text)
+
+ - if @reply_allowed
+ - comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
+ - unless comments.empty?
+ = render "projects/notes/diff_notes_with_reply", notes: comments, line: line.text
+
+ - if last_line > 0
+ = render "projects/diffs/match_line", {line: "",
+ line_old: last_line, line_new: last_line, bottom: true}
+
+- if diff_file.diff.blank? && diff_file.mode_changed?
+ .file-mode-changed
+ File mode changed
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
new file mode 100644
index 0000000000..47abbba2eb
--- /dev/null
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -0,0 +1,19 @@
+.alert.alert-warning
+ %h4
+ Too many changes.
+ .pull-right
+ - unless diff_hard_limit_enabled?
+ = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-sm btn-warning"
+
+ - if current_controller?(:commit) or current_controller?(:merge_requests)
+ - if current_controller?(:commit)
+ = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-sm"
+ = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-warning btn-sm"
+ - elsif @merge_request && @merge_request.persisted?
+ = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-warning btn-sm"
+ = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-sm"
+ %p
+ To preserve performance only
+ %strong #{allowed_diff_size} of #{diffs.size}
+ files are displayed.
+
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index d9acb68551..fbf04847e4 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -3,17 +3,17 @@
.project-edit-content
%div
%h3.page-title
- Project settings:
- %p.light Some settings, such as "Transfer Project", are hidden inside the danger area below.
+ Project settings
%hr
.panel-body
- = form_for @project, remote: true, html: { class: "edit_project form-horizontal" } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal" }, authenticity_token: true do |f|
+
%fieldset
.form-group.project_name_holder
= f.label :name, class: 'control-label' do
Project name
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control"
+ = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
.form-group
@@ -31,14 +31,11 @@
= render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project)
- %fieldset.features
- %legend
- Tags:
- .form-group
- = f.label :tag_list, "Tags", class: 'control-label'
- .col-sm-10
- = f.text_field :tag_list, maxlength: 2000, class: "form-control"
- %p.hint Separate tags with commas.
+ .form-group
+ = f.label :tag_list, "Tags", class: 'control-label'
+ .col-sm-10
+ = f.text_field :tag_list, maxlength: 2000, class: "form-control"
+ %p.help-block Separate tags with commas.
%fieldset.features
%legend
@@ -50,15 +47,6 @@
= f.check_box :issues_enabled
%span.descr Lightweight issue tracking system for this project
- - if Project.issues_tracker.values.count > 1
- .form-group
- = f.label :issues_tracker, "Issues tracker", class: 'control-label'
- .col-sm-10= f.select(:issues_tracker, project_issues_trackers(@project.issues_tracker), {}, { disabled: !@project.issues_enabled })
-
- .form-group
- = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
- .col-sm-10= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?, class: 'form-control'
-
.form-group
= f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
.col-sm-10
@@ -80,111 +68,139 @@
= f.check_box :snippets_enabled
%span.descr Share code pastes with others out of git repository
+ %fieldset.features
+ %legend
+ Project avatar:
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ - if @project.avatar?
+ = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
+ %p.light
+ - if @project.avatar_in_git
+ Project avatar in repository: #{ @project.avatar_in_git }
+ %p.light
+ - if @project.avatar?
+ You can change your project avatar here
+ - else
+ You can upload a project avatar here
+ %a.choose-btn.btn.btn-sm.js-choose-project-avatar-button
+ %i.icon-paper-clip
+ %span Choose File ...
+
+ %span.file_name.js-avatar-filename File name...
+ = f.file_field :avatar, class: "js-project-avatar-input hidden"
+ .light The maximum file size allowed is 200KB.
+ - if @project.avatar?
+ %hr
+ = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
- .danger-settings.js-toggle-container
- .centered-light-block
- %h3
- %i.icon-warning-sign
- Dangerous settings
-
- %p Project settings below may result in data loss!
- = link_to '#', class: 'btn js-toggle-button' do
- Show them to me
- %i.icon-chevron-down
-
- .js-toggle-content.hide
- - if can? current_user, :archive_project, @project
- .panel.panel-default.panel.panel-warning
+ .danger-settings
+ - if can? current_user, :archive_project, @project
+ - if @project.archived?
+ .panel.panel-success
.panel-heading
- - if @project.archived?
- Unarchive project
- - else
- Archive project
+ Unarchive project
.panel-body
- - if @project.archived?
- %p
- Unarchiving the project will mark its repository as active.
- %br
- The project can be committed to.
- %br
- %strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive', unarchive_project_path(@project),
- data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
- method: :post, class: "btn btn-remove"
- - else
- %p
- Archiving the project will mark its repository as read-only.
- %br
- It is hidden from the dashboard and doesn't show up in searches.
- %br
- %strong Archived projects cannot be committed to!
- = link_to 'Archive', archive_project_path(@project),
- data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
- method: :post, class: "btn btn-warning"
+ %p
+ Unarchiving the project will mark its repository as active.
+ %br
+ The project can be committed to.
+ %br
+ %strong Once active this project shows up in the search and on the dashboard.
+ = link_to 'Unarchive', unarchive_namespace_project_path(@project.namespace, @project),
+ data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
+ method: :post, class: "btn btn-success"
- else
- .nothing-here-block Only the project owner can archive a project
+ .panel.panel-warning
+ .panel-heading
+ Archive project
+ .panel-body
+ %p
+ Archiving the project will mark its repository as read-only.
+ %br
+ It is hidden from the dashboard and doesn't show up in searches.
+ %br
+ %strong Archived projects cannot be committed to!
+ = link_to 'Archive', archive_namespace_project_path(@project.namespace, @project),
+ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
+ method: :post, class: "btn btn-warning"
+ - else
+ .nothing-here-block Only the project owner can archive a project
- .panel.panel-default.panel.panel-warning
- .panel-heading Rename repository
+ .panel.panel-default.panel.panel-warning
+ .panel-heading Rename repository
+ .errors-holder
+ .panel-body
+ = form_for([@project.namespace.becomes(Namespace), @project], html: { class: 'form-horizontal' }) do |f|
+ .form-group.project_name_holder
+ = f.label :name, class: 'control-label' do
+ Project name
+ .col-sm-9
+ .form-group
+ = f.text_field :name, placeholder: "Example Project", class: "form-control"
+ .form-group
+ = f.label :path, class: 'control-label' do
+ %span Path
+ .col-sm-9
+ .form-group
+ .input-group
+ .input-group-addon
+ #{URI.join(root_url, @project.namespace.path)}/
+ = f.text_field :path, class: 'form-control'
+ %span.input-group-addon .git
+ %ul
+ %li Be careful. Renaming a project's repository can have unintended side effects.
+ %li You will need to update your local repositories to point to the new location.
+ .form-actions
+ = f.submit 'Rename', class: "btn btn-warning"
+
+ - if can?(current_user, :change_namespace, @project)
+ .panel.panel-default.panel.panel-danger
+ .panel-heading Transfer project
.errors-holder
.panel-body
- = form_for(@project, html: { class: 'form-horizontal' }) do |f|
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
.form-group
- = f.label :path, class: 'control-label' do
- %span Path
- .col-sm-9
+ = label_tag :new_namespace_id, nil, class: 'control-label' do
+ %span Namespace
+ .col-sm-10
.form-group
- .input-group
- = f.text_field :path, class: 'form-control'
- %span.input-group-addon .git
+ = select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
%ul
- %li Be careful. Renaming a project's repository can have unintended side effects.
+ %li Be careful. Changing the project's namespace can have unintended side effects.
+ %li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location.
.form-actions
- = f.submit 'Rename', class: "btn btn-warning"
+ = f.submit 'Transfer', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+ - else
+ .nothing-here-block Only the project owner can transfer a project
- - if can?(current_user, :change_namespace, @project)
- .panel.panel-default.panel.panel-danger
- .panel-heading Transfer project
- .errors-holder
- .panel-body
- = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
- .form-group
- = f.label :namespace_id, class: 'control-label' do
- %span Namespace
- .col-sm-10
- .form-group
- = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' }
- %ul
- %li Be careful. Changing the project's namespace can have unintended side effects.
- %li You can only transfer the project to namespaces you manage.
- %li You will need to update your local repositories to point to the new location.
- .form-actions
- = f.submit 'Transfer', class: "btn btn-remove"
- - else
- .nothing-here-block Only the project owner can transfer a project
-
- - if can?(current_user, :remove_project, @project)
- .panel.panel-default.panel.panel-danger
- .panel-heading Remove project
- .panel-body
+ - if can?(current_user, :remove_project, @project)
+ .panel.panel-default.panel.panel-danger
+ .panel-heading Remove project
+ .panel-body
+ = form_tag(namespace_project_path(@project.namespace, @project), method: :delete, html: { class: 'form-horizontal'}) do
%p
Removing the project will delete its repository and all related resources including issues, merge requests etc.
%br
%strong Removed projects cannot be restored!
- = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove"
- - else
- .nothing-here-block Only project owner can remove a project
+ = link_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+ - else
+ .nothing-here-block Only project owner can remove a project
.save-project-loader.hide
.center
%h2
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Saving project.
%p Please wait a moment, this page will automatically refresh when ready.
+
+
+= render 'shared/confirm_modal', phrase: @project.path
diff --git a/app/views/projects/edit_tree/_diff.html.haml b/app/views/projects/edit_tree/_diff.html.haml
deleted file mode 100644
index cf044feb9a..0000000000
--- a/app/views/projects/edit_tree/_diff.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-%table.text-file
- - each_diff_line(diff, 1) do |line, type, line_code, line_new, line_old, raw_line|
- %tr.line_holder{ id: line_code, class: "#{type}" }
- - if type == "match"
- %td.old_line= "..."
- %td.new_line= "..."
- %td.line_content.matched= line
- - else
- %td.old_line
- = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code
- %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code
- %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
-
diff --git a/app/views/projects/edit_tree/preview.html.haml b/app/views/projects/edit_tree/preview.html.haml
deleted file mode 100644
index 87ce5dc31d..0000000000
--- a/app/views/projects/edit_tree/preview.html.haml
+++ /dev/null
@@ -1,26 +0,0 @@
-.diff-file
- .diff-content
- - if gitlab_markdown?(@blob.name)
- .file-content.wiki
- = preserve do
- = markdown(@content)
- - elsif markup?(@blob.name)
- .file-content.wiki
- = raw render_markup(@blob.name, @content)
- - else
- .file-content.code
- - unless @diff.empty?
- %table.text-file
- - @diff.each do |line, type, line_code, line_new, line_old, raw_line|
- %tr.line_holder{ id: line_code, class: "#{type}" }
- - if type == "match"
- %td.old_line= "..."
- %td.new_line= "..."
- %td.line_content.matched= line
- - else
- %td.old_line
- = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code
- %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code
- %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
- - else
- .nothing-here-block No changes.
diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml
deleted file mode 100644
index 05050e7df7..0000000000
--- a/app/views/projects/edit_tree/show.html.haml
+++ /dev/null
@@ -1,81 +0,0 @@
-.file-editor
- %ul.nav.nav-tabs.js-edit-mode
- %li.active
- = link_to 'Edit', '#editor'
- %li
- = link_to editing_preview_title(@blob.name), '#preview', 'data-preview-url' => preview_project_edit_tree_path(@project, @id)
-
- = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do
- .file-holder.file
- .file-title
- %i.icon-file
- %span.file_name
- %span.monospace.light #{@ref}:
- = @path
- %span.options
- .btn-group.tree-btn-group
- = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message }
- .file-content.code
- %pre.js-edit-mode-pane#editor
- .js-edit-mode-pane#preview.hide
- .center
- %h2
- %i.icon-spinner.icon-spin
-
- .form-group.commit_message-group
- = label_tag 'commit_message', class: "control-label" do
- Commit message
- .col-sm-10
- = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message', '',
- placeholder: "Update #{@blob.name}", required: true, rows: 3, class: 'form-control')}
- .form-actions
- = hidden_field_tag 'last_commit', @last_commit
- = hidden_field_tag 'content', '', id: "file-content"
- = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
- .commit-button-annotation
- = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary'
- .message
- to branch
- %strong= @ref
- = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message}
-
-:javascript
- ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace")
- var ace_mode = "#{@blob.language.try(:ace_mode)}";
- var editor = ace.edit("editor");
- editor.setValue("#{escape_javascript(@blob.data)}");
- if (ace_mode) {
- editor.getSession().setMode('ace/mode/' + ace_mode);
- }
-
- disableButtonIfEmptyField("#commit_message", ".js-commit-button");
-
- $(".js-commit-button").click(function(){
- $("#file-content").val(editor.getValue());
- $(".file-editor form").submit();
- });
-
- var editModePanes = $('.js-edit-mode-pane'),
- editModeLinks = $('.js-edit-mode a');
-
- editModeLinks.click(function(event) {
- event.preventDefault();
-
- var currentLink = $(this),
- paneId = currentLink.attr('href'),
- currentPane = editModePanes.filter(paneId);
-
- editModeLinks.parent().removeClass('active hover');
- currentLink.parent().addClass('active hover');
- editModePanes.hide();
-
- if (paneId == '#preview') {
- currentPane.fadeIn(200);
- $.post(currentLink.data('preview-url'), { content: editor.getValue() }, function(response) {
- currentPane.empty().append(response);
- })
- } else {
- currentPane.fadeIn(200);
- editor.focus()
- }
- })
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 97dc73bce1..49806ceaa9 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,28 +1,43 @@
+- if current_user && can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+ = render 'shared/no_password'
+
= render "home_panel"
+.center.well
+ %h3
+ The repository for this project is empty
+ %h4
+ You can
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, 'master'), class: 'btn btn-new btn-lg' do
+ add a file
+ or do a push via the command line.
+
+%h4
+ %strong Command line instructions
%div.git-empty
%fieldset
- %legend Git global setup:
+ %legend Git global setup
%pre.dark
:preserve
git config --global user.name "#{git_user_name}"
git config --global user.email "#{git_user_email}"
%fieldset
- %legend Create Repository
+ %legend Create a new repository
%pre.dark
:preserve
mkdir #{@project.path}
cd #{@project.path}
git init
- touch README
- git add README
- git commit -m 'first commit'
+ touch README.md
+ git add README.md
+ git commit -m "first commit"
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
git push -u origin master
%fieldset
- %legend Existing Git Repo?
+ %legend Push an existing Git repository
%pre.dark
:preserve
cd existing_git_repo
@@ -31,4 +46,4 @@
- if can? current_user, :remove_project, @project
.prepend-top-20
- = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
+ = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml
deleted file mode 100644
index bdd75de447..0000000000
--- a/app/views/projects/fork.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-.alert.alert-danger.alert-block
- %h4
- %i.icon-code-fork
- Fork Error!
- %p
- You tried to fork
- = link_to_project @project
- but it failed for the following reason:
-
-
- - if @forked_project && @forked_project.errors.any?
- %p
- –
- = @forked_project.errors.full_messages.first
-
- %p
- = link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
- %i.icon-code-fork
- Try to Fork again
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
new file mode 100644
index 0000000000..8eb4f79597
--- /dev/null
+++ b/app/views/projects/forks/error.html.haml
@@ -0,0 +1,20 @@
+- if @forked_project && !@forked_project.saved?
+ .alert.alert-danger.alert-block
+ %h4
+ %i.fa.fa-code-fork
+ Fork Error!
+ %p
+ You tried to fork
+ = link_to_project @project
+ but it failed for the following reason:
+
+
+ - if @forked_project && @forked_project.errors.any?
+ %p
+ –
+ = @forked_project.errors.full_messages.first
+
+ %p
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
+ %i.fa.fa-code-fork
+ Try to Fork again
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
new file mode 100644
index 0000000000..5a6c46f320
--- /dev/null
+++ b/app/views/projects/forks/new.html.haml
@@ -0,0 +1,39 @@
+%h3.page-title Fork project
+%p.lead
+ Click to fork the project to a user or group
+%hr
+
+.fork-namespaces
+ - @namespaces.in_groups_of(6, false) do |group|
+ .row
+ - group.each do |namespace|
+ .col-md-2.col-sm-3
+ - if fork = namespace.find_fork_of(@project)
+ .thumbnail.fork-exists-thumbnail
+ = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
+ = image_tag namespace_icon(namespace, 200)
+ .caption
+ %h4=namespace.human_name
+ %p
+ = namespace.path
+ - else
+ .thumbnail.fork-thumbnail
+ = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+ = image_tag namespace_icon(namespace, 200)
+ .caption
+ %h4=namespace.human_name
+ %p
+ = namespace.path
+
+ %p.light
+ Fork is a copy of a project repository.
+ %br
+ Forking a repository allows you to do changes without affecting the original project.
+
+.save-project-loader.hide
+ .center
+ %h2
+ %i.fa.fa-spinner.fa-spin
+ Forking repository
+ %p Please wait a moment, this page will automatically refresh when ready.
+
diff --git a/app/views/projects/go_import.html.haml b/app/views/projects/go_import.html.haml
new file mode 100644
index 0000000000..87ac75a350
--- /dev/null
+++ b/app/views/projects/go_import.html.haml
@@ -0,0 +1,5 @@
+!!! 5
+%html
+ %head
+ - web_url = [Gitlab.config.gitlab.url, @namespace, @id].join('/')
+ %meta{name: "go-import", content: "#{web_url.split('://')[1]} git #{web_url}.git"}
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
new file mode 100644
index 0000000000..9383df1330
--- /dev/null
+++ b/app/views/projects/graphs/_head.html.haml
@@ -0,0 +1,5 @@
+%ul.nav.nav-tabs
+ = nav_link(action: :show) do
+ = link_to 'Contributors', namespace_project_graph_path
+ = nav_link(action: :commits) do
+ = link_to 'Commits', commits_namespace_project_graph_path
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
new file mode 100644
index 0000000000..78b4c1923d
--- /dev/null
+++ b/app/views/projects/graphs/commits.html.haml
@@ -0,0 +1,85 @@
+= render 'head'
+
+%p.lead
+ Commit statistics for
+ %strong #{@repository.root_ref}
+ #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
+
+.row
+ .col-md-6
+ %ul
+ %li
+ %p.lead
+ %strong #{@commits_graph.commits.size}
+ commits during
+ %strong #{@commits_graph.duration}
+ days
+ %li
+ %p.lead
+ Average
+ %strong #{@commits_graph.commit_per_day}
+ commits per day
+ %li
+ %p.lead
+ Contributed by
+ %strong #{@commits_graph.authors}
+ authors
+ .col-md-6
+ %div
+ %p.slead
+ Commits per day of month
+ %canvas#month-chart{width: 800, height: 400}
+.row
+ .col-md-6
+ %div
+ %p.slead
+ Commits per day hour (UTC)
+ %canvas#hour-chart{width: 800, height: 400}
+ .col-md-6
+ %div
+ %p.slead
+ Commits per weekday
+ %canvas#weekday-chart{width: 800, height: 400}
+
+:coffeescript
+ data = {
+ labels : #{@commits_per_time.keys.to_json},
+ datasets : [{
+ fillColor : "rgba(220,220,220,0.5)",
+ strokeColor : "rgba(220,220,220,1)",
+ pointColor : "rgba(220,220,220,1)",
+ pointStrokeColor : "#EEE",
+ data : #{@commits_per_time.values.to_json}
+ }]
+ }
+
+ ctx = $("#hour-chart").get(0).getContext("2d");
+ new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2})
+
+ data = {
+ labels : #{@commits_per_week_days.keys.to_json},
+ datasets : [{
+ fillColor : "rgba(220,220,220,0.5)",
+ strokeColor : "rgba(220,220,220,1)",
+ pointColor : "rgba(220,220,220,1)",
+ pointStrokeColor : "#EEE",
+ data : #{@commits_per_week_days.values.to_json}
+ }]
+ }
+
+ ctx = $("#weekday-chart").get(0).getContext("2d");
+ new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2})
+
+ data = {
+ labels : #{@commits_per_month.keys.to_json},
+ datasets : [{
+ fillColor : "rgba(220,220,220,0.5)",
+ strokeColor : "rgba(220,220,220,1)",
+ pointColor : "rgba(220,220,220,1)",
+ pointStrokeColor : "#EEE",
+ data : #{@commits_per_month.values.to_json}
+ }]
+ }
+
+ ctx = $("#month-chart").get(0).getContext("2d");
+ new Chart(ctx).Line(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2})
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 8e4548f26d..e3d5094ddc 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,17 +1,13 @@
+= render 'head'
.loading-graph
.center
%h3.page-title
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Building repository graph.
- %p Please wait a moment, this page will automatically refresh when ready.
+ %p.slead Please wait a moment, this page will automatically refresh when ready.
-.stat-graph
+.stat-graph.hide
.header.clearfix
- .pull-right
- %select
- %option{:value => "commits"} Commits
- %option{:value => "additions"} Additions
- %option{:value => "deletions"} Deletions
%h3#date_header.page-title
%p.light
Commits to #{@project.default_branch}, excluding merge commits. Limited by 6,000 commits
@@ -21,15 +17,21 @@
#contributors.clearfix
%ol.contributors-list.clearfix
-:javascript
- $(".stat-graph").hide();
- $.ajax({
+
+:coffeescript
+ $.ajax
type: "GET",
url: location.href,
- complete: function() {
+ success: (data) ->
+ graph = new ContributorsStatGraph()
+ graph.init(data)
+
+ $("#brush_change").change ->
+ graph.change_date_header()
+ graph.redraw_authors()
+
$(".stat-graph").fadeIn();
$(".loading-graph").hide();
- },
- dataType: "script"
- });
+ dataType: "json"
+
diff --git a/app/views/projects/graphs/show.js.haml b/app/views/projects/graphs/show.js.haml
deleted file mode 100644
index dcf6cacdce..0000000000
--- a/app/views/projects/graphs/show.js.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-- if @success
- :plain
- controller = new ContributorsStatGraph
- controller.init(#{@log})
-
- $("select").change( function () {
- var field = $(this).val()
- controller.set_current_field(field)
- controller.redraw_master()
- controller.redraw_authors()
- })
-
- $("#brush_change").change( function () {
- controller.change_date_header()
- controller.redraw_authors()
- })
-- else
- :plain
- $('.stat-graph').replaceWith('
Failed to load graph
')
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 9a003c87f6..bbaddba31b 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -7,7 +7,7 @@
%hr.clearfix
-= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-horizontal' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
-if @hook.errors.any?
.alert.alert-danger
- @hook.errors.full_messages.each do |msg|
@@ -58,8 +58,8 @@
- @hooks.each do |hook|
%li
.pull-right
- = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small btn-grouped"
- = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small btn-grouped"
+ = link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
+ = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
.clearfix
%span.monospace= hook.url
%p
diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml
deleted file mode 100644
index 9efb1658c2..0000000000
--- a/app/views/projects/import.html.haml
+++ /dev/null
@@ -1,30 +0,0 @@
-- if @project.import_in_progress?
- .save-project-loader
- .center
- %h2
- %i.icon-spinner.icon-spin
- Import in progress.
- %p.monospace git clone --bare #{@project.import_url}
- %p Please wait while we import the repository for you. Refresh at will.
- :javascript
- new ProjectImport();
-
-- elsif @project.import_failed?
- .save-project-loader
- .center
- %h2
- Import failed. Retry?
- %hr
- - if can?(current_user, :admin_project, @project)
- = form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
- .form-group.import-url-data
- = f.label :import_url, class: 'control-label' do
- %span Import existing repo
- .col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
- .bs-callout.bs-callout-info
- This url must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
- %br
- The import will time out after 4 minutes. For big repositories, use a clone/push combination.
- .form-actions
- = f.submit 'Retry import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
new file mode 100644
index 0000000000..934b6b8c01
--- /dev/null
+++ b/app/views/projects/imports/new.html.haml
@@ -0,0 +1,21 @@
+%h3.page-title
+ - if @project.import_failed?
+ Import failed. Retry?
+ - else
+ Import repository
+
+%hr
+
+= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
+ .form-group.import-url-data
+ = f.label :import_url, class: 'control-label' do
+ %span Import existing git repo
+ .col-sm-10
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
+ .well.prepend-top-20
+ This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
+ %br
+ The import will time out after 4 minutes. For big repositories, use a clone/push combination.
+ For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
+ .form-actions
+ = f.submit 'Start import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml
new file mode 100644
index 0000000000..2d1fdafed2
--- /dev/null
+++ b/app/views/projects/imports/show.html.haml
@@ -0,0 +1,9 @@
+.save-project-loader
+ .center
+ %h2
+ %i.fa.fa-spinner.fa-spin
+ Import in progress.
+ %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
+ %p Please wait while we import the repository for you. Refresh at will.
+ :javascript
+ new ProjectImport();
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
new file mode 100644
index 0000000000..288b48f458
--- /dev/null
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -0,0 +1,33 @@
+- content_for :note_actions do
+ - if can?(current_user, :modify_issue, @issue)
+ - if @issue.closed?
+ = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue'
+ - else
+ = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue"
+.row
+ %section.col-md-9
+ .votes-holder.pull-right
+ #votes= render 'votes/votes_block', votable: @issue
+ .participants
+ %span= pluralize(@issue.participants(current_user).count, 'participant')
+ - @issue.participants(current_user).each do |participant|
+ = link_to_member(@project, participant, name: false, size: 24)
+ .voting_notes#notes= render "projects/notes/notes_with_form"
+ %aside.col-md-3
+ .issuable-affix
+ .clearfix
+ %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @issue)
+ %hr
+ .context
+ = render partial: 'issue_context', locals: { issue: @issue }
+
+ - if @issue.labels.any?
+ .issuable-context-title
+ %label Labels
+ .issue-show-labels
+ - @issue.labels.each do |label|
+ = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do
+ = render_colored_label(label)
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index b2a8e8e091..7d7217eb2a 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,67 +1,9 @@
%div.issue-form-holder
- %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}"
+ %h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}"
%hr
- - if @repository.exists? && !@repository.empty? && @repository.contribution_guide && !@issue.persisted?
- - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
- .row
- .col-sm-10.col-sm-offset-2
- .alert.alert-info
- = "Please review the #{link_to "guidelines for contribution", contribution_guide_url} to this repository.".html_safe
- = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f|
- -if @issue.errors.any?
- .row
- .col-sm-10.col-sm-offset-2
- .alert.alert-danger
- - @issue.errors.full_messages.each do |msg|
- %span= msg
- %br
- .form-group
- = f.label :title, class: 'control-label' do
- %strong= 'Title *'
- .col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control js-gfm-input", autofocus: true, required: true
- .form-group
- = f.label :description, 'Description', class: 'control-label'
- .col-sm-10
- = f.text_area :description, class: 'form-control js-gfm-input markdown-area', rows: 14
- .col-sm-12.hint
- .pull-left Issues are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
- .clearfix
- .error-alert
- %hr
- .form-group
- .issue-assignee
- = f.label :assignee_id, class: 'control-label' do
- %i.icon-user
- Assign to
- .col-sm-10
- = project_users_select_tag('issue[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @issue.assignee_id)
-
- = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
- .form-group
- .issue-milestone
- = f.label :milestone_id, class: 'control-label' do
- %i.icon-time
- Milestone
- .col-sm-10= f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2'})
-
- .form-group
- = f.label :label_ids, class: 'control-label' do
- %i.icon-tag
- Labels
- .col-sm-10
- = f.collection_select :label_ids, @project.labels.all, :id, :name, { selected: @issue.label_ids }, multiple: true, class: 'select2'
-
- .form-actions
- - if @issue.new_record?
- = f.submit 'Submit new issue', class: "btn btn-create"
- -else
- = f.submit 'Save changes', class: "btn-save btn"
-
- - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue)
- = link_to "Cancel", cancel_path, class: 'btn btn-cancel'
+ = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
+ = render 'projects/issuable_form', f: f, issuable: @issue
:javascript
$('.assign-to-me-link').on('click', function(e){
@@ -69,4 +11,4 @@
e.preventDefault();
});
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
deleted file mode 100644
index dad547d4eb..0000000000
--- a/app/views/projects/issues/_head.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-%ul.nav.nav-tabs
- = nav_link(controller: :issues) do
- = link_to project_issues_path(@project), class: "tab" do
- Browse Issues
- = nav_link(controller: :milestones) do
- = link_to 'Milestones', project_milestones_path(@project), class: "tab"
- = nav_link(controller: :labels) do
- = link_to 'Labels', project_labels_path(@project), class: "tab"
-
- - if current_controller?(:milestones)
- %li.pull-right
- %button.btn.btn-default.sidebar-expand-button
- %i.icon.icon-list
-
- - if current_controller?(:issues)
- - if current_user
- %li
- = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
- %i.icon-rss
-
- %li.pull-right
- .pull-right
- %button.btn.btn-default.sidebar-expand-button
- %i.icon.icon-list
- = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
- .append-right-10.hidden-xs.hidden-sm
- = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
- = hidden_field_tag :state, params['state']
- = hidden_field_tag :scope, params['scope']
- = hidden_field_tag :assignee_id, params['assignee_id']
- = hidden_field_tag :milestone_id, params['milestone_id']
- = hidden_field_tag :label_id, params['label_id']
- - if can? current_user, :write_issue, @project
- = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
- %i.icon-plus
- New Issue
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index db28b83118..998e74d12c 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,46 +1,52 @@
-%li{ id: dom_id(issue), class: issue_css_classes(issue), url: project_issue_path(issue.project, issue) }
+%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) }
- if controller.controller_name == 'issues'
.issue-check
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue)
.issue-title
- %span.light= "##{issue.iid}"
%span.str-truncated
- = link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title"
- - if issue.closed?
- %small.pull-right
- = "CLOSED"
+ = link_to_gfm issue.title, issue_path(issue), class: "row_title"
+ .pull-right.light
+ - if issue.closed?
+ %span
+ CLOSED
+ - note_count = issue.notes.user.count
+ - if note_count > 0
+
+ %span
+ %i.fa.fa-comments
+ = note_count
.issue-info
+ = link_to "##{issue.iid}", issue_path(issue), class: "light"
- if issue.assignee
assigned to #{link_to_member(@project, issue.assignee)}
- - else
- unassigned
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- - if issue.notes.any?
- %span
- %i.icon-comments
- = issue.notes.count
- if issue.milestone
%span
- %i.icon-time
+ %i.fa.fa-clock-o
= issue.milestone.title
- .pull-right
+ - if issue.tasks?
+ %span.task-status
+ = issue.task_status
+
+ .pull-right.issue-updated-at
%small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
.issue-labels
- issue.labels.each do |label|
- = render_colored_label(label)
+ = link_to namespace_project_issues_path(issue.project.namespace, issue.project, label_name: label.name) do
+ = render_colored_label(label)
.issue-actions
- if can? current_user, :modify_issue, issue
- if issue.closed?
- = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue btn-reopen", remote: true
+ = link_to 'Reopen', issue_path(issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-sm btn-grouped reopen_issue btn-reopen", remote: true
- else
- = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true
- = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do
- %i.icon-edit
+ = link_to 'Close', issue_path(issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-sm btn-grouped close_issue btn-close", remote: true
+ = link_to edit_namespace_project_issue_path(issue.project.namespace, issue.project, issue), class: "btn btn-sm edit-issue-link btn-grouped" do
+ %i.fa.fa-pencil-square-o
Edit
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
index d7987f43fb..9228074d83 100644
--- a/app/views/projects/issues/_issue_context.html.haml
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -1,24 +1,46 @@
-= form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
- .row
- .col-sm-6
- %strong.append-right-10
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
+ %div.prepend-top-20
+ .issuable-context-title
+ %label
Assignee:
-
- - if can?(current_user, :modify_issue, @issue)
- = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @issue.assignee_id)
- - elsif issue.assignee
- = link_to_member(@project, @issue.assignee)
+ - if issue.assignee
+ %strong= link_to_member(@project, @issue.assignee, size: 24)
- else
- None
+ none
+ - if can?(current_user, :modify_issue, @issue)
+ = users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id, null_user: true, first_user: true)
- .col-sm-6.text-right
- %strong.append-right-10
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
Milestone:
- - if can?(current_user, :modify_issue, @issue)
- = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact'})
- = hidden_field_tag :issue_context
- = f.submit class: 'btn'
- - elsif issue.milestone
- = link_to issue.milestone.title, project_milestone_path
+ - if issue.milestone
+ %span.back-to-milestone
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @issue.milestone) do
+ %strong
+ %i.fa.fa-clock-o
+ = @issue.milestone.title
- else
- None
+ none
+ - if can?(current_user, :modify_issue, @issue)
+ = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
+ = hidden_field_tag :issue_context
+ = f.submit class: 'btn'
+
+ - if current_user
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
+ Subscription:
+ %button.btn.btn-block.subscribe-button
+ %i.fa.fa-eye
+ %span= @issue.subscribed?(current_user) ? "Unsubscribe" : "Subscribe"
+ - subscribtion_status = @issue.subscribed?(current_user) ? "subscribed" : "unsubscribed"
+ .subscription-status{"data-status" => subscribtion_status}
+ .description-block.unsubscribed{class: ( "hidden" if @issue.subscribed?(current_user) )}
+ You're not receiving notifications from this thread.
+ .description-block.subscribed{class: ( "hidden" unless @issue.subscribed?(current_user) )}
+ You're receiving notifications because you're subscribed to this thread.
+
+:coffeescript
+ new Subscription("#{toggle_subscription_namespace_project_issue_path(@issue.project.namespace, @project, @issue)}")
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 1d0dcd7f07..5d243adb5f 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,66 +1,3 @@
-.append-bottom-10
- .check-all-holder
- = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left"
- .issues-filters
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.icon-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.icon-time
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
-
- .clearfix
- .issues_bulk_update.hide
- = form_tag bulk_update_project_issues_path(@project), method: :post do
- = select_tag('update[status]', options_for_select([['Open', 'open'], ['Closed', 'closed']]), prompt: "Status")
- = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee')
- = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
- = hidden_field_tag 'update[issues_ids]', []
- = hidden_field_tag :status, params[:status]
- = button_tag "Update issues", class: "btn update_selected_issues btn-save"
-
.panel.panel-default
%ul.well-list.issues-list
= render @issues
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 012ba23595..126f2c07fa 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,23 +1,12 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@project.name} issues"
- xml.link :href => project_issues_url(@project, :atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => project_issues_url(@project), :rel => "alternate", :type => "text/html"
- xml.id project_issues_url(@project)
+ xml.link :href => namespace_project_issues_url(@project.namespace, @project, :atom), :rel => "self", :type => "application/atom+xml"
+ xml.link :href => namespace_project_issues_url(@project.namespace, @project), :rel => "alternate", :type => "text/html"
+ xml.id namespace_project_issues_url(@project.namespace, @project)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(@project, issue)
- xml.link :href => project_issue_url(@project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 5de77b8bf3..d3c7ae24a7 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,9 +1,19 @@
-= render "head"
-.row
- .fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
- %i.icon-list.icon-2x
- .col-md-3.responsive-side
- = render 'shared/project_filter', project_entities_path: project_issues_path(@project),
- labels: true, redirect: 'issues', entity: 'issue'
- .col-md-9.issues-holder
- = render "issues"
+.append-bottom-10
+ .pull-right
+ .pull-left
+ - if current_user
+ .hidden-xs.pull-left
+ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
+ %i.fa.fa-rss
+
+ = render 'shared/issuable_search_form', path: namespace_project_issues_path(@project.namespace, @project)
+
+ - if can? current_user, :write_issue, @project
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
+ %i.fa.fa-plus
+ New Issue
+
+ = render 'shared/issuable_filter'
+
+.issues-holder
+ = render "issues"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index bd5f01ff6a..bd28d8a1db 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,73 +1,40 @@
-%h3.page-title
- Issue ##{@issue.iid}
+.issue
+ .issue-details
+ %h4.page-title
+ .issue-box{ class: issue_box_class(@issue) }
+ - if @issue.closed?
+ Closed
+ - else
+ Open
+ Issue ##{@issue.iid}
+ %small.creator
+ · created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
- %span.pull-right.issue-btn-group
- - if can?(current_user, :write_issue, @project)
- = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do
- %i.icon-plus
- New Issue
- - if can?(current_user, :modify_issue, @issue)
- - if @issue.closed?
- = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
- - else
- = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
+ .pull-right
+ - if can?(current_user, :write_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-grouped new-issue-link", title: "New Issue", id: "new_issue_link" do
+ %i.fa.fa-plus
+ New Issue
+ - if can?(current_user, :modify_issue, @issue)
+ - if @issue.closed?
+ = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
+ - else
+ = link_to 'Close', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
- = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do
- %i.icon-edit
- Edit
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: "btn btn-grouped issuable-edit" do
+ %i.fa.fa-pencil-square-o
+ Edit
-.clearfix
- .votes-holder
- #votes= render 'votes/votes_block', votable: @issue
+ %hr
+ %h2.issue-title
+ = gfm escape_once(@issue.title)
+ %div
+ - if @issue.description.present?
+ .description
+ .wiki
+ = preserve do
+ = markdown(@issue.description, parse_tasks: true)
- .back-link
- = link_to project_issues_path(@project) do
- ← To issues list
- %span.milestone-nav-link
- - if @issue.milestone
- |
- %span.light Milestone
- = link_to project_milestone_path(@project, @issue.milestone) do
- = @issue.milestone.title
-
-.issue-box{ class: issue_box_class(@issue) }
- .state.clearfix
- .state-label
- - if @issue.closed?
- Closed
- - else
- Open
-
- .creator
- Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)}
-
- %h4.title
- = gfm escape_once(@issue.title)
-
- - if @issue.description.present?
- .description
- .wiki
- = preserve do
- = markdown @issue.description
- .context
- %cite.cgray
- = render partial: 'issue_context', locals: { issue: @issue }
-
-
-- content_for :note_actions do
- - if can?(current_user, :modify_issue, @issue)
- - if @issue.closed?
- = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
- - else
- = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
-
-.participants
- %cite.cgray #{@issue.participants.count} participants
- - @issue.participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
-
- .issue-show-labels.pull-right
- - @issue.labels.each do |label|
- = render_colored_label(label)
-
-.voting_notes#notes= render "projects/notes/notes_with_form"
+ %hr
+ .issue-discussion
+ = render "projects/issues/discussion"
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 5199e9fc61..1d38662bff 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -3,8 +3,15 @@
:plain
$("##{dom_id(@issue)}").fadeOut();
- elsif params[:issue_context]
- $('.issue-box .context').effect('highlight');
+ $('.context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}");
+ $('.context').effect('highlight');
- if @issue.milestone
- $('.milestone-nav-link').replaceWith("| Milestone #{escape_javascript(link_to @issue.milestone.title, project_milestone_path(@issue.project, @issue.milestone))}")
+ $('.milestone-nav-link').replaceWith("| Milestone #{escape_javascript(link_to @issue.milestone.title, namespace_project_milestone_path(@issue.project.namespace, @issue.project, @issue.milestone))}")
- else
$('.milestone-nav-link').html('')
+
+
+$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
+$('.edit-issue.inline-update input[type="submit"]').hide();
+new UsersSelect()
+new Issue();
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 72a01e1c27..261d52dedc 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -1,8 +1,8 @@
-= form_for [@project, @label], html: { class: 'form-horizontal label-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form' } do |f|
-if @label.errors.any?
.row
.col-sm-10.col-sm-offset-2
- .bs-callout.bs-callout-danger
+ .alert.alert-danger
- @label.errors.full_messages.each do |msg|
%span= msg
%br
@@ -16,9 +16,9 @@
.col-sm-10
.input-group
.input-group-addon.label-color-preview
- = f.text_field :color, placeholder: "#AA33EE", class: "form-control"
+ = f.color_field :color, class: "form-control"
.help-block
- 6 character hex values starting with a # sign.
+ Choose any color.
%br
Or you can choose one of suggested colors below
@@ -29,5 +29,5 @@
.form-actions
= f.submit 'Save', class: 'btn btn-save js-save-button'
- = link_to "Cancel", project_labels_path(@project), class: 'btn btn-cancel'
+ = link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel'
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 03a8f0921b..8282945286 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -2,9 +2,9 @@
= render_colored_label(label)
.pull-right
%strong.append-right-20
- = link_to project_issues_path(@project, label_name: label.name) do
+ = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do
= pluralize label.open_issues_count, 'open issue'
- if can? current_user, :admin_label, @project
- = link_to 'Edit', edit_project_label_path(@project, label), class: 'btn'
- = link_to 'Remove', project_label_path(@project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+ = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn'
+ = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml
new file mode 100644
index 0000000000..1b4c83ab09
--- /dev/null
+++ b/app/views/projects/labels/destroy.js.haml
@@ -0,0 +1,2 @@
+- if @project.labels.size == 0
+ $('.labels').load(document.URL + ' .light-well').hide().fadeIn(1000)
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 52435c5d89..e003d1dfe7 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -2,7 +2,7 @@
Edit label
%span.light #{@label.name}
.back-link
- = link_to project_labels_path(@project) do
+ = link_to namespace_project_labels_path(@project.namespace, @project) do
← To labels list
%hr
= render 'form'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 075779a9c8..0700e72d39 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,17 +1,15 @@
-= render "projects/issues/head"
-
- if can? current_user, :admin_label, @project
- = link_to new_project_label_path(@project), class: "pull-right btn btn-new" do
+ = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
New label
%h3.page-title
Labels
%hr
-- if @labels.present?
- %ul.bordered-list.manage-labels-list
- = render @labels
- = paginate @labels, theme: 'gitlab'
-
-- else
- .light-well
- .nothing-here-block Create first label or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels
+.labels
+ - if @labels.present?
+ %ul.bordered-list.manage-labels-list
+ = render @labels
+ = paginate @labels, theme: 'gitlab'
+ - else
+ .light-well
+ .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 850da0b192..0683ed5d4f 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,6 +1,6 @@
%h3 New label
.back-link
- = link_to project_labels_path(@project) do
+ = link_to namespace_project_labels_path(@project.namespace, @project) do
← To labels list
%hr
= render 'form'
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
new file mode 100644
index 0000000000..eb72eaabd8
--- /dev/null
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -0,0 +1,31 @@
+- content_for :note_actions do
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ - if @merge_request.open?
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
+ - if @merge_request.closed?
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
+
+.row
+ %section.col-md-9
+ .votes-holder.pull-right
+ #votes= render 'votes/votes_block', votable: @merge_request
+ = render "projects/merge_requests/show/participants"
+ = render "projects/notes/notes_with_form"
+ %aside.col-md-3
+ .issuable-affix
+ .clearfix
+ %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @merge_request)
+ %hr
+ .context
+ = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
+
+ - if @merge_request.labels.any?
+ .issuable-context-title
+ %label Labels
+ .merge-request-show-labels
+ - @merge_request.labels.each do |label|
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project, label_name: label.name) do
+ = render_colored_label(label)
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 0af89b6e37..1c7160bce5 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,69 +1,6 @@
-= form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal" } do |f|
- .row
- .col-sm-2
- .col-sm-10
- - if @repository.contribution_guide && !@merge_request.persisted?
- - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
- .alert.alert-info
- Please review the
- %strong #{link_to "guidelines for contribution", contribution_guide_url}
- to this repository.
-
- -if @merge_request.errors.any?
- .alert.alert-danger
- - @merge_request.errors.full_messages.each do |msg|
- %div= msg
-
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f|
.merge-request-form-info
- .form-group
- = f.label :title, class: 'control-label' do
- %strong= "Title *"
- .col-sm-10= f.text_field :title, class: "form-control pad js-gfm-input", maxlength: 255, rows: 5, required: true
- .form-group
- = f.label :description, "Description", class: 'control-label'
- .col-sm-10
- = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 14
- .col-sm-12.hint
- .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
- .clearfix
- .error-alert
- %hr
- .form-group
- .issue-assignee
- = f.label :assignee_id, class: 'control-label' do
- %i.icon-user
- Assign to
- .col-sm-10
- = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id)
-
- = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
- .form-group
- .issue-milestone
- = f.label :milestone_id, class: 'control-label' do
- %i.icon-time
- Milestone
- .col-sm-10= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
-
-
- .form-group
- = f.label :label_ids, class: 'control-label' do
- %i.icon-tag
- Labels
- .col-sm-10
- = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2'
-
- .form-actions
- - if @merge_request.new_record?
- = f.submit 'Submit merge request', class: "btn btn-create"
- -else
- = f.submit 'Save changes', class: "btn btn-save"
- - if @merge_request.new_record?
- = link_to project_merge_requests_path(@source_project), class: "btn btn-cancel" do
- Cancel
- - else
- = link_to project_merge_request_path(@target_project, @merge_request), class: "btn btn-cancel" do
- Cancel
+ = render 'projects/issuable_form', f: f, issuable: @merge_request
:javascript
disableButtonIfEmptyField("#merge_request_title", ".btn-save");
@@ -72,4 +9,4 @@
e.preventDefault();
});
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
index 35a86e6511..19e4dab874 100644
--- a/app/views/projects/merge_requests/_head.html.haml
+++ b/app/views/projects/merge_requests/_head.html.haml
@@ -1,5 +1,5 @@
.top-tabs
- = link_to project_merge_requests_path(@project), class: "tab #{'active' if current_page?(project_merge_requests_path(@project)) }" do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), class: "tab #{'active' if current_page?(namespace_project_merge_requests_path(@project.namespace, @project)) }" do
%span
Merge Requests
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 7f5de232dc..4f30d1e69f 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,37 +1,48 @@
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
- %span.light= "##{merge_request.iid}"
- = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title"
- - if merge_request.merged?
- %small.pull-right
- %i.icon-ok
- = "MERGED"
- - else
- %span.pull-right
- - if merge_request.for_fork?
- %span.light
- #{merge_request.source_project_namespace}:
- = truncate merge_request.source_branch, length: 25
- %i.icon-angle-right.light
- = merge_request.target_branch
+ %span.str-truncated
+ = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+ .pull-right.light
+ - if merge_request.merged?
+ %span
+ %i.fa.fa-check
+ MERGED
+ - elsif merge_request.closed?
+ %span
+ %i.fa.fa-close
+ CLOSED
+ - else
+ %span.hidden-xs.hidden-sm
+ %span.label-branch<
+ %i.fa.fa-code-fork
+ %span= merge_request.target_branch
+ - note_count = merge_request.mr_and_commit_notes.user.count
+ - if note_count > 0
+
+ %span
+ %i.fa.fa-comments
+ = note_count
.merge-request-info
- - if merge_request.author
- authored by #{link_to_member(merge_request.source_project, merge_request.author)}
+ = link_to "##{merge_request.iid}", merge_request_path(merge_request), class: "light"
+ - if merge_request.assignee
+ assigned to #{link_to_member(merge_request.source_project, merge_request.assignee)}
+ - else
+ Unassigned
- if merge_request.votes_count > 0
= render 'votes/votes_inline', votable: merge_request
- - if merge_request.notes.any?
- %span
- %i.icon-comments
- = merge_request.mr_and_commit_notes.count
- if merge_request.milestone_id?
%span
- %i.icon-time
+ %i.fa.fa-clock-o
= merge_request.milestone.title
+ - if merge_request.tasks?
+ %span.task-status
+ = merge_request.task_status
- .pull-right
+ .pull-right.hidden-xs
%small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
.merge-request-labels
- merge_request.labels.each do |label|
- = render_colored_label(label)
+ = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, label_name: label.name) do
+ = render_colored_label(label)
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
new file mode 100644
index 0000000000..b8a0ca9a42
--- /dev/null
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -0,0 +1,13 @@
+.panel.panel-default
+ %ul.well-list.mr-list
+ = render @merge_requests
+ - if @merge_requests.blank?
+ %li
+ .nothing-here-block No merge requests to show
+
+- if @merge_requests.present?
+ .pull-right
+ %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
+
+ = paginate @merge_requests, theme: "gitlab"
+
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 9972617215..17e76059fd 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -1,7 +1,7 @@
%h3.page-title Compare branches for new Merge Request
%hr
-= form_for [@project, @merge_request], url: new_project_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline" } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline" } do |f|
.hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row
.col-md-6
@@ -60,19 +60,19 @@
, target_branch = $("#merge_request_target_branch")
, target_project = $("#merge_request_target_project_id");
- $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });
- $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
+ $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: source_branch.val() });
+ $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
target_project.on("change", function() {
- $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });
+ $.get("#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: $(this).val() });
});
source_branch.on("change", function() {
- $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });
+ $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: $(this).val() });
$(".mr-compare-errors").fadeOut();
$(".mr-compare-btn").enable();
});
target_branch.on("change", function() {
- $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
+ $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
$(".mr-compare-errors").fadeOut();
$(".mr-compare-btn").enable();
});
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 7c43d35598..4e72458932 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -7,84 +7,105 @@
%strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
%span.pull-right
- = link_to 'Change branches', new_project_merge_request_path(@project)
+ = link_to 'Change branches', new_namespace_project_merge_request_path(@project.namespace, @project)
-= form_for [@project, @merge_request], html: { class: "merge-request-form" } do |f|
- .panel.panel-default
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: "merge-request-form form-horizontal gfm-form" } do |f|
+ .merge-request-form-info
+ .form-group
+ = f.label :title, class: 'control-label' do
+ %strong Title *
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, autofocus: true, class: 'form-control pad js-gfm-input', required: true
+ .form-group.issuable-description
+ = f.label :description, 'Description', class: 'control-label'
+ .col-sm-10
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
- .panel-body
- .form-group
- .light
- = f.label :title do
- = "Title *"
- = f.text_field :title, class: "form-control input-lg js-gfm-input", maxlength: 255, rows: 5, required: true
- .form-group
- .light
- = f.label :description, "Description"
- = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 10
- .clearfix.hint
- .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
- .error-alert
- .form-group
- .issue-assignee
- = f.label :assignee_id do
- %i.icon-user
- Assign to
- %div
- = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
-
- = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
- .form-group
- .issue-milestone
- = f.label :milestone_id do
- %i.icon-time
- Milestone
- %div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
- .form-group
- = f.label :label_ids do
- %i.icon-tag
- Labels
- %div
- = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2'
+ .col-sm-12-hint
+ .pull-left
+ Parsed with
+ #{link_to 'Gitlab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
+ .pull-right
+ Attach files by dragging & dropping
+ or #{link_to 'selecting them', '#', class: 'markdown-selector'}.
- .panel-footer
- - if contribution_guide_url(@target_project)
+ .clearfix
+ .error-alert
+ %hr
+ .form-group
+ .issue-assignee
+ = f.label :assignee_id, class: 'control-label' do
+ %i.fa.fa-user
+ Assign to
+ .col-sm-10
+ = users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
+
+ = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
+ .form-group
+ .issue-milestone
+ = f.label :milestone_id, class: 'control-label' do
+ %i.fa.fa-clock-o
+ Milestone
+ .col-sm-10
+ - if milestone_options(@merge_request).present?
+ = f.select(:milestone_id, milestone_options(@merge_request), {include_blank: 'Select milestone'}, {class: 'select2'})
+ - else
+ %span.light No open milestones available.
+
+ - if can? current_user, :admin_milestone, @merge_request.target_project
+ = link_to 'Create new milestone', new_namespace_project_milestone_path(@merge_request.target_project.namespace, @merge_request.target_project), target: :blank
+ .form-group
+ = f.label :label_ids, class: 'control-label' do
+ %i.fa.fa-tag
+ Labels
+ .col-sm-10
+ - if @merge_request.target_project.labels.any?
+ = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, {selected: @merge_request.label_ids}, multiple: true, class: 'select2'
+ - else
+ %span.light No labels yet.
+
+ - if can? current_user, :admin_label, @merge_request.target_project
+ = link_to 'Create new label', new_namespace_project_label_path(@merge_request.target_project.namespace, @merge_request.target_project), target: :blank
+
+ .form-actions
+ - if guide_url = contribution_guide_url(@target_project)
%p
Please review the
- %strong #{link_to "guidelines for contribution", contribution_guide_url(@target_project)}
+ %strong #{link_to 'guidelines for contribution', guide_url}
to this repository.
= f.hidden_field :source_project_id
+ = f.hidden_field :source_branch
= f.hidden_field :target_project_id
= f.hidden_field :target_branch
- = f.hidden_field :source_branch
- = f.submit 'Submit merge request', class: "btn btn-create"
+ = f.submit 'Submit merge request', class: 'btn btn-create'
-.mr-compare
- %div.panel.panel-default
- .panel-heading
- Commits (#{@commits.count})
- - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- %ul.well-list
- - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit|
- = render "projects/commits/inline_commit", commit: commit, project: @project
- %li.warning-row.unstyled
- other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
+.mr-compare.merge-request
+ %ul.nav.nav-tabs.merge-request-tabs
+ %li.commits-tab{data: {action: 'commits', toggle: 'tab'}}
+ = link_to url_for(params) do
+ %i.fa.fa-history
+ Commits
+ %span.badge= @commits.size
+ %li.diffs-tab{data: {action: 'diffs', toggle: 'tab'}}
+ = link_to url_for(params) do
+ %i.fa.fa-list-alt
+ Changes
+ %span.badge= @diffs.size
+
+ .commits.tab-content
+ = render "projects/commits/commits", project: @project
+ .diffs.tab-content
+ - if @diffs.present?
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project
+ - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ .alert.alert-danger
+ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
+ %p To preserve performance the line changes are not shown.
- else
- %ul.well-list= render Commit.decorate(@commits), project: @project
-
- %h4 Changes
- - if @diffs.present?
- = render "projects/commits/diffs", diffs: @diffs, project: @project
- - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- .bs-callout.bs-callout-danger
- %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
- %p To preserve performance the line changes are not shown.
- - else
- .bs-callout.bs-callout-danger
- %h4 This comparison includes huge diff.
- %p To preserve performance the line changes are not shown.
-
+ .alert.alert-danger
+ %h4 This comparison includes a huge diff.
+ %p To preserve performance the line changes are not shown.
:javascript
$('.assign-to-me-link').on('click', function(e){
@@ -92,4 +113,11 @@
e.preventDefault();
});
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
+
+:javascript
+ var merge_request
+ merge_request = new MergeRequest({
+ action: 'commits'
+ });
+
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 8fca9f0212..a74aede4e6 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,46 +1,76 @@
-.merge-request
- = render "projects/merge_requests/show/mr_title"
- = render "projects/merge_requests/show/how_to_merge"
- = render "projects/merge_requests/show/mr_box"
- = render "projects/merge_requests/show/state_widget"
- = render "projects/merge_requests/show/commits"
- = render "projects/merge_requests/show/participants"
+.merge-request{'data-url' => merge_request_path(@merge_request)}
+ .merge-request-details
+ = render "projects/merge_requests/show/mr_title"
+ %hr
+ = render "projects/merge_requests/show/mr_box"
+ %hr
+ .append-bottom-20
+ .slead
+ %span From
+ - if @merge_request.for_fork?
+ %strong.label-branch<
+ - if @merge_request.source_project
+ = link_to @merge_request.source_project_namespace, namespace_project_path(@merge_request.source_project.namespace, @merge_request.source_project)
+ - else
+ \ #{@merge_request.source_project_namespace}
+ \:#{@merge_request.source_branch}
+ %span into
+ %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
+ - else
+ %strong.label-branch #{@merge_request.source_branch}
+ %span into
+ %strong.label-branch #{@merge_request.target_branch}
+ - if @merge_request.open?
+ %span.pull-right
+ .btn-group
+ %a.btn.dropdown-toggle{ data: {toggle: :dropdown} }
+ %i.fa.fa-download
+ Download as
+ %span.caret
+ %ul.dropdown-menu
+ %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
+ %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
+
+ = render "projects/merge_requests/show/how_to_merge"
+ = render "projects/merge_requests/show/state_widget"
- if @commits.present?
- %ul.nav.nav-pills.merge-request-tabs
+ %ul.nav.nav-tabs.merge-request-tabs
%li.notes-tab{data: {action: 'notes'}}
- = link_to project_merge_request_path(@project, @merge_request) do
- %i.icon-comment
+ = link_to merge_request_path(@merge_request) do
+ %i.fa.fa-comments
Discussion
- %span.badge= @merge_request.mr_and_commit_notes.count
+ %span.badge= @merge_request.mr_and_commit_notes.user.count
+ %li.commits-tab{data: {action: 'commits'}}
+ = link_to merge_request_path(@merge_request), title: 'Commits' do
+ %i.fa.fa-history
+ Commits
+ %span.badge= @commits.size
%li.diffs-tab{data: {action: 'diffs'}}
- = link_to diffs_project_merge_request_path(@project, @merge_request) do
- %i.icon-list-alt
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) do
+ %i.fa.fa-list-alt
Changes
%span.badge= @merge_request.diffs.size
- - content_for :note_actions do
- - if can?(current_user, :modify_merge_request, @merge_request)
- - unless @merge_request.closed? || @merge_request.merged?
- = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link", title: "Close merge request"
- - if @merge_request.closed?
- = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
-
+ .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
+ = render "projects/merge_requests/discussion"
+ .commits.tab-content
+ = render "projects/merge_requests/show/commits"
.diffs.tab-content
- if current_page?(action: 'diffs')
= render "projects/merge_requests/show/diffs"
- .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
- = render "projects/notes/notes_with_form"
+
.mr-loading-status
= spinner
+
:javascript
var merge_request;
merge_request = new MergeRequest({
- url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}",
+ url_to_automerge_check: "#{automerge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
- url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}",
+ url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
ci_enable: #{@project.ci_service ? "true" : "false"},
current_status: "#{@merge_request.merge_status_name}",
action: "#{controller.action_name}"
diff --git a/app/views/projects/merge_requests/automerge.js.haml b/app/views/projects/merge_requests/automerge.js.haml
index e01ff662e7..a53cbb150a 100644
--- a/app/views/projects/merge_requests/automerge.js.haml
+++ b/app/views/projects/merge_requests/automerge.js.haml
@@ -1,7 +1,6 @@
-if @status
:plain
- location.reload();
+ merge_request.mergeInProgress();
-else
:plain
merge_request.alreadyOrCannotBeMerged()
-
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 0954fa8fce..d7992bdd19 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,78 +1,11 @@
-- if can? current_user, :write_merge_request, @project
- = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do
- %i.icon-plus
- New Merge Request
-%h3.page-title
- Merge Requests
-%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.icon-list.icon-2x
- .col-md-3.responsive-side
- = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
- labels: true, redirect: 'merge_requests', entity: 'merge_request'
- .col-md-9
- .mr-filters.append-bottom-10
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.icon-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
+.append-bottom-10
+ .pull-right
+ = render 'shared/issuable_search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.icon-time
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
-
- .panel.panel-default
- %ul.well-list.mr-list
- = render @merge_requests
- - if @merge_requests.blank?
- %li
- .nothing-here-block No merge requests to show
- - if @merge_requests.present?
- .pull-right
- %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
-
- = paginate @merge_requests, theme: "gitlab"
-
-:javascript
- $(merge_requestsPage);
+ - if can? current_user, :write_merge_request, @project
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do
+ %i.fa.fa-plus
+ New Merge Request
+ = render 'shared/issuable_filter'
+.merge-requests-holder
+ = render 'merge_requests'
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index ede709ea1d..3b7f283daf 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,30 +1 @@
-- if @commits.present?
- .panel.panel-default
- .panel-heading
- %i.icon-list
- Commits (#{@commits.count})
- .commits.mr-commits
- - if @commits.count > 8
- %ul.first-commits.well-list
- - @commits.first(8).each do |commit|
- = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
- %li.bottom
- 8 of #{@commits.count} commits displayed.
- %strong
- %a.show-all-commits Click here to show all
- - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- %ul.all-commits.hide.well-list
- - @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit|
- = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
- %li
- other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
- - else
- %ul.all-commits.hide.well-list
- - @commits.each do |commit|
- = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
-
- - else
- %ul.well-list
- - @commits.each do |commit|
- = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
-
+= render "projects/commits/commits", project: @merge_request.source_project
diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml
index ab00b34242..105562fb05 100644
--- a/app/views/projects/merge_requests/show/_context.html.haml
+++ b/app/views/projects/merge_requests/show/_context.html.haml
@@ -1,24 +1,48 @@
-= form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
- .row
- .col-sm-6
- %strong.append-right-10
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
+ %div.prepend-top-20
+ .issuable-context-title
+ %label
Assignee:
-
- - if can?(current_user, :modify_merge_request, @merge_request)
- = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @merge_request.assignee_id)
- - elsif merge_request.assignee
- = link_to_member(@project, @merge_request.assignee)
+ - if @merge_request.assignee
+ %strong= link_to_member(@project, @merge_request.assignee, size: 24)
- else
- None
-
- .col-sm-6.text-right
- %strong.append-right-10
- Milestone:
+ none
+ .issuable-context-selectbox
- if can?(current_user, :modify_merge_request, @merge_request)
- = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact'})
+ = users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id, null_user: true)
+
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
+ Milestone:
+ - if @merge_request.milestone
+ %span.back-to-milestone
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @merge_request.milestone) do
+ %strong
+ %i.fa.fa-clock-o
+ = @merge_request.milestone.title
+ - else
+ none
+ .issuable-context-selectbox
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :merge_request_context
= f.submit class: 'btn'
- - elsif merge_request.milestone
- = link_to merge_request.milestone.title, project_milestone_path
- - else
- None
+
+ - if current_user
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
+ Subscription:
+ %button.btn.btn-block.subscribe-button
+ %i.fa.fa-eye
+ %span= @merge_request.subscribed?(current_user) ? "Unsubscribe" : "Subscribe"
+ - subscribtion_status = @merge_request.subscribed?(current_user) ? "subscribed" : "unsubscribed"
+ .subscription-status{"data-status" => subscribtion_status}
+ .description-block.unsubscribed{class: ( "hidden" if @merge_request.subscribed?(current_user) )}
+ You're not receiving notifications from this thread.
+ .description-block.subscribed{class: ( "hidden" unless @merge_request.subscribed?(current_user) )}
+ You're receiving notifications because you're subscribed to this thread.
+
+:coffeescript
+ new Subscription("#{toggle_subscription_namespace_project_merge_request_path(@merge_request.project.namespace, @project, @merge_request)}")
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index eb63b68106..786b5f3906 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,12 +1,12 @@
- if @merge_request_diff.collected?
- = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
+ = render "projects/diffs/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
- else
- .bs-callout.bs-callout-warning
+ .alert.alert-warning
%h4
Changes view for this comparison is extremely large.
%p
You can
- = link_to "download it", project_merge_request_path(@merge_request.target_project, @merge_request, format: :diff), class: "vlink"
+ = link_to "download it", merge_request_path(@merge_request, format: :diff), class: "vlink"
instead.
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 9540453ce3..63db4b3096 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -10,11 +10,11 @@
- target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
%p
%strong Step 1.
- Checkout the branch we are going to merge and pull in the code
+ Fetch the code and create a new branch pointing to it
%pre.dark
:preserve
- git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch}
- git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
%p
%strong Step 2.
Merge the branch and push the changes to GitLab
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index d7d5f970c9..9f51f84d40 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -12,34 +12,25 @@
- if @show_merge_controls
.automerge_widget.can_be_merged.hide
.clearfix
- = form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f|
- %h4
- You can accept this request automatically.
- .accept-merge-holder.clearfix
- .js-toggle-container
- %p
- You can
- %strong= link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
- before accepting merge request
- .js-toggle-content.hide
- .form-group
- = label_tag :merge_commit_message, "Commit message", class: 'control-label'
- .col-sm-10
- = render 'shared/commit_message_container', {textarea: text_area_tag(:merge_commit_message,
- @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true)}
- %p.hint
- The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines.
+ = form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post do |f|
+ .accept-merge-holder.clearfix.js-toggle-container
+ .accept-action
+ = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
+ - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
+ .accept-control.checkbox
+ = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
+ = check_box_tag :should_remove_source_branch
+ Remove source-branch
+ .accept-control
+ = link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do
+ %i.fa.fa-edit
+ Modify commit message
+ .js-toggle-content.hide.prepend-top-20
+ = render 'shared/commit_message_container', params: params,
+ text: @merge_request.merge_commit_message,
+ rows: 14, hint: true
- .accept-group
- .pull-left
- = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
- - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
- .remove_branch_holder.pull-left
- = label_tag :should_remove_source_branch, class: "checkbox" do
- = check_box_tag :should_remove_source_branch
- Remove source-branch
-
- %hr
+ %br
.light
If you still want to merge this request manually - use
%strong
@@ -54,15 +45,22 @@
.automerge_widget.cannot_be_merged.hide
%h4
This request can't be merged with GitLab.
- %p
You should do it manually with
%strong
- = link_to "command line", "#modal_merge_info", class: "how_to_merge_link", title: "How To Merge", "data-toggle" => "modal"
+ = link_to "#modal_merge_info", class: "underlined-link how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" do
+ command line
+
+ %p
+ %button.btn.disabled
+ %i.fa.fa-warning
+ Accept Merge Request
+
+ This usually happens when git can not resolve conflicts between branches automatically.
.automerge_widget.unchecked
%p
%strong
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Checking for ability to automatically merge…
.automerge_widget.already_cannot_be_merged.hide
@@ -71,6 +69,6 @@
.merge-in-progress.hide
%p
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Merge is in progress. Please wait. Page will be automatically reloaded.
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index f1aaba2010..ada9ae58b8 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,25 +1,9 @@
-.issue-box{ class: issue_box_class(@merge_request) }
- .state.clearfix
- .state-label
- - if @merge_request.merged?
- Merged
- - elsif @merge_request.closed?
- Closed
- - else
- Open
-
- .creator
- Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
-
- %h4.title
- = gfm escape_once(@merge_request.title)
+%h2.issue-title
+ = gfm escape_once(@merge_request.title)
+%div
- if @merge_request.description.present?
.description
.wiki
= preserve do
- = markdown @merge_request.description
-
- .context
- %cite.cgray
- = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
+ = markdown(@merge_request.description, parse_tasks: true)
diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml
index b77eeac612..ffa3f7b0e3 100644
--- a/app/views/projects/merge_requests/show/_mr_ci.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml
@@ -1,29 +1,34 @@
- if @commits.any?
.ci_widget.ci-success{style: "display:none"}
- %i.icon-ok
+ %i.fa.fa-check
%span CI build passed
for #{@merge_request.last_commit_short_sha}.
- = link_to "Build page", ci_build_details_path(@merge_request)
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
.ci_widget.ci-failed{style: "display:none"}
- %i.icon-remove
+ %i.fa.fa-times
%span CI build failed
for #{@merge_request.last_commit_short_sha}.
- = link_to "Build page", ci_build_details_path(@merge_request)
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
- [:running, :pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"}
- %i.icon-time
+ %i.fa.fa-clock-o
%span CI build #{status}
for #{@merge_request.last_commit_short_sha}.
- = link_to "Build page", ci_build_details_path(@merge_request)
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
.ci_widget
- %strong
- %i.icon-spinner
- Checking for CI status for #{@merge_request.last_commit_short_sha}
+ %i.fa.fa-spinner
+ Checking for CI status for #{@merge_request.last_commit_short_sha}
+
+ .ci_widget.ci-canceled{style: "display:none"}
+ %i.fa.fa-times
+ %span CI build canceled
+ for #{@merge_request.last_commit_short_sha}.
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
.ci_widget.ci-error{style: "display:none"}
- %i.icon-remove
+ %i.fa.fa-times
%span Cannot connect to the CI server. Please check your settings and try again.
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 563a524499..46e92a9c55 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,40 +1,22 @@
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: issue_box_class(@merge_request) }
+ - if @merge_request.merged?
+ Merged
+ - elsif @merge_request.closed?
+ Closed
+ - else
+ Open
= "Merge Request ##{@merge_request.iid}"
+ %small.creator
+ ·
+ created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
- %span.pull-right.issue-btn-group
+ .issue-btn-group.pull-right
- if can?(current_user, :modify_merge_request, @merge_request)
- if @merge_request.open?
- .btn-group.pull-left
- %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
- %i.icon-download-alt
- Download as
- %span.caret
- %ul.dropdown-menu
- %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch)
- %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff)
-
- = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
-
- = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do
- %i.icon-edit
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do
+ %i.fa.fa-pencil-square-o
Edit
- if @merge_request.closed?
- = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
-
-.votes-holder.hidden-sm.hidden-xs
- #votes= render 'votes/votes_block', votable: @merge_request
-
-.back-link
- = link_to project_merge_requests_path(@project) do
- ← To merge requests
-
- %span.prepend-left-20
- %span From
- - if @merge_request.for_fork?
- %strong.label-branch #{@merge_request.source_project_namespace}:#{@merge_request.source_branch}
- %span into
- %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
- - else
- %strong.label-branch #{@merge_request.source_branch}
- %span into
- %strong.label-branch #{@merge_request.target_branch}
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml
index 007c111f7f..9c93fa55fe 100644
--- a/app/views/projects/merge_requests/show/_participants.html.haml
+++ b/app/views/projects/merge_requests/show/_participants.html.haml
@@ -1,8 +1,4 @@
.participants
- %cite.cgray #{@merge_request.participants.count} participants
- - @merge_request.participants.each do |participant|
+ %span #{@merge_request.participants(current_user).count} participants
+ - @merge_request.participants(current_user).each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
-
- .merge-request-show-labels.pull-right
- - @merge_request.labels.each do |label|
- = render_colored_label(label)
diff --git a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
index 491360af1b..59cb85edfc 100644
--- a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
+++ b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
@@ -4,14 +4,14 @@
- elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && @merge_request.merged?
.remove_source_branch_widget
%p Changes merged into #{@merge_request.target_branch}. You can remove source branch now
- = link_to project_branch_path(@merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-small remove_source_branch" do
- %i.icon-remove
+ = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
+ %i.fa.fa-times
Remove Source Branch
.remove_source_branch_widget.failed.hide
Failed to remove source branch '#{@merge_request.source_branch}'
.remove_source_branch_in_progress.hide
- %i.icon-refresh.icon-spin
+ %i.fa.fa-spinner.fa-spin
Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded.
diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml
index c30310f125..44bd9347f5 100644
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -1,8 +1,8 @@
-.panel.mr-state-widget.panel-default
+.mr-state-widget
- if @merge_request.source_project.ci_service && @commits.any?
- .panel-heading
+ .mr-widget-heading
= render "projects/merge_requests/show/mr_ci"
- .panel-body
+ .mr-widget-body
- if @merge_request.open?
- if @merge_request.source_branch_exists? && @merge_request.target_branch_exists?
= render "projects/merge_requests/show/mr_accept"
@@ -11,16 +11,26 @@
- if @merge_request.closed?
%h4
- Closed by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
- %p Changes was not merged into target branch
+ Closed
+ - if @merge_request.closed_event
+ by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
+ %p Changes were not merged into target branch
- if @merge_request.merged?
%h4
- Merged by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
+ Merged
+ - if @merge_request.merge_event
+ by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch"
+ - if @merge_request.locked?
+ %h4
+ Merge in progress...
+ %p
+ Merging is in progress. While merging this request is locked and cannot be closed.
+
- unless @commits.any?
%h4 Nothing to merge
%p
@@ -31,11 +41,10 @@
%br
Try to use different branches or push new code.
- - if !@closes_issues.empty? && @merge_request.open?
- .panel-footer
+ - if @closes_issues.present? && @merge_request.open?
+ .mr-widget-footer
%span
- %i.icon-ok
+ %i.fa.fa-check
Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
= succeed '.' do
!= gfm(issues_sentence(@closes_issues))
-
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index 6452cc6382..b4df1d2073 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,2 +1,8 @@
- if params[:merge_request_context]
- $('.issue-box .context').effect('highlight');
+ $('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}");
+ $('.context').effect('highlight');
+
+ new UsersSelect()
+
+ $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true});
+ merge_request = new MergeRequest();
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index df79125eae..95b7070ce5 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,11 +1,11 @@
%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}"
.back-link
- = link_to project_milestones_path(@project) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project) do
← To milestones
%hr
-= form_for [@project, @milestone], html: {class: "new_milestone form-horizontal"} do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
%ul
@@ -18,13 +18,14 @@
.col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control"
%p.hint Required
- .form-group
+ .form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
- = f.text_area :description, maxlength: 65535, class: "form-control markdown-area", rows: 10
- .hint
- .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ .hint
+ .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
+ .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
.clearfix
.error-alert
.col-md-6
@@ -37,10 +38,10 @@
.form-actions
- if @milestone.new_record?
= f.submit 'Create milestone', class: "btn-create btn"
- = link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_milestones_path(@project.namespace, @project), class: "btn btn-cancel"
-else
= f.submit 'Save changes', class: "btn-save btn"
- = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel"
:javascript
@@ -50,4 +51,4 @@
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
index b5ec0fc988..88fccfe498 100644
--- a/app/views/projects/milestones/_issue.html.haml
+++ b/app/views/projects/milestones/_issue.html.haml
@@ -1,9 +1,9 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => project_issue_path(@project, issue) }
- %span.str-truncated
- = link_to [@project, issue] do
- %span.cgray ##{issue.iid}
- = link_to_gfm issue.title, [@project, issue], title: issue.title
+%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
+ = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
+ %span
+ = link_to [@project.namespace.becomes(Namespace), @project, issue] do
+ %span.cgray ##{issue.iid}
+ = link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
index d54cb3f8e7..0d7a118569 100644
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ b/app/views/projects/milestones/_merge_request.html.haml
@@ -1,5 +1,8 @@
-%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => project_merge_request_path(@project, merge_request) }
+%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) }
%span.str-truncated
- = link_to [@project, merge_request] do
+ = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do
%span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [@project, merge_request], title: merge_request.title
+ = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
+ .pull-right.assignee-icon
+ - if merge_request.assignee
+ = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 4018d132a5..62360158ff 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,27 +1,24 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
.pull-right
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do
- %i.icon-edit
+ = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-sm edit-milestone-link btn-grouped" do
+ %i.fa.fa-pencil-square-o
Edit
- = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-close"
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close"
%h4
- = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone)
+ = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- if milestone.expired? and not milestone.closed?
%span.cred (Expired)
%small
= milestone.expires_at
- - if milestone.is_empty?
- %span.muted Empty
- - else
- %div
- %div
- = link_to project_issues_path(milestone.project, milestone_id: milestone.id) do
- = pluralize milestone.issues.count, 'Issue'
-
- = link_to project_merge_requests_path(milestone.project, milestone_id: milestone.id) do
- = pluralize milestone.merge_requests.count, 'Merge Request'
-
- %span.light #{milestone.percent_complete}% complete
- .progress.progress-info
- .progress-bar{style: "width: #{milestone.percent_complete}%;"}
+ .row
+ .col-sm-6
+ = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
+ = pluralize milestone.issues.count, 'Issue'
+
+ = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
+ = pluralize milestone.merge_requests.count, 'Merge Request'
+
+ %span.light #{milestone.percent_complete}% complete
+ .col-sm-6
+ = milestone_progress_bar(milestone)
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index f0e48a5177..d3eab8d6d7 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,33 +1,17 @@
-= render "projects/issues/head"
-.milestones_content
- %h3.page-title
- Milestones
- - if can? current_user, :admin_milestone, @project
- = link_to new_project_milestone_path(@project), class: "pull-right btn btn-new", title: "New Milestone" do
- %i.icon-plus
- New Milestone
+.pull-right
+ - if can? current_user, :admin_milestone, @project
+ = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
+ %i.fa.fa-plus
+ New Milestone
+= render 'shared/milestones_filter'
- .row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
- %i.icon-list.icon-2x
- .col-md-3.responsive-side
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if (params[:f] == "active" || !params[:f]))}
- = link_to project_milestones_path(@project, f: "active") do
- Active
- %li{class: ("active" if params[:f] == "closed")}
- = link_to project_milestones_path(@project, f: "closed") do
- Closed
- %li{class: ("active" if params[:f] == "all")}
- = link_to project_milestones_path(@project, f: "all") do
- All
- .col-md-9
- .panel.panel-default
- %ul.well-list
- = render @milestones
+.milestones
+ .panel.panel-default
+ %ul.well-list
+ = render @milestones
- - if @milestones.blank?
- %li
- .nothing-here-block No milestones to show
+ - if @milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
- = paginate @milestones, theme: "gitlab"
+ = paginate @milestones, theme: "gitlab"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 42c3f45f6c..25cc003096 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,55 +1,49 @@
-= render "projects/issues/head"
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: issue_box_class(@milestone) }
+ - if @milestone.closed?
+ Closed
+ - elsif @milestone.expired?
+ Expired
+ - else
+ Open
Milestone ##{@milestone.iid}
+ %small.creator
+ = @milestone.expires_at
.pull-right
- if can?(current_user, :admin_milestone, @project)
- = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
- %i.icon-edit
+ = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
+ %i.fa.fa-pencil-square-o
Edit
- if @milestone.active?
- = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- else
- = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+ = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success
%span All issues for this milestone are closed. You may close milestone now.
-.back-link
- = link_to project_milestones_path(@project) do
- ← To milestones list
-
-
-.issue-box{ class: issue_box_class(@milestone) }
- .state.clearfix
- .state-label
- - if @milestone.closed?
- Closed
- - elsif @milestone.expired?
- Expired
- - else
- Open
- .creator
- = @milestone.expires_at
-
- %h4.title
- = gfm escape_once(@milestone.title)
-
+%h3.issue-title
+ = gfm escape_once(@milestone.title)
+%div
- if @milestone.description.present?
.description
.wiki
= preserve do
= markdown @milestone.description
- .context
- %p
- Progress:
- #{@milestone.closed_items_count} closed
- –
- #{@milestone.open_items_count} open
- %span.pull-right= @milestone.expires_at
- .progress.progress-info
- .progress-bar{style: "width: #{@milestone.percent_complete}%;"}
+%hr
+.context
+ %p.lead
+ Progress:
+ #{@milestone.closed_items_count} closed
+ –
+ #{@milestone.open_items_count} open
+
+ %span.light #{@milestone.percent_complete}% complete
+ %span.pull-right= @milestone.expires_at
+ = milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
@@ -66,11 +60,12 @@
Participants
%span.badge= @users.count
- .pull-right
- = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-grouped", title: "New Issue" do
- %i.icon-plus
- New Issue
- = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link btn-grouped"
+ - if @project.issues_enabled
+ .pull-right
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
+ %i.fa.fa-plus
+ New Issue
+ = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 5310822823..c36bad1e94 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,10 +1,10 @@
= render "head"
.project-network
.controls
- = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
- = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: "search-input form-control input-mx-250"
- = button_tag type: 'submit', class: 'btn btn-success' do
- %i.icon-search
+ = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
+ = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha'
+ = button_tag class: 'btn btn-success btn-search-sha' do
+ %i.fa.fa-search
.inline.prepend-left-20
.checkbox.light
= label_tag :filter_ref do
@@ -15,9 +15,12 @@
= spinner nil, true
:javascript
- new Network({
- url: '#{project_network_path(@project, @ref, @options.merge(format: :json))}',
- commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}',
+ disableButtonIfEmptyField('#extended_sha1', '.btn-search-sha')
+
+ network_graph = new Network({
+ url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}',
+ commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}',
ref: '#{@ref}',
commit_id: '#{@commit.id}'
})
+ new ShortcutsNetwork(network_graph.branch_graph)
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 7efaf5a087..a06c85b425 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -3,12 +3,15 @@
= render 'projects/errors'
.project-edit-content
- = form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f|
+ = form_for @project, html: { class: 'new_project form-horizontal' } do |f|
.form-group.project-name-holder
- = f.label :name, class: 'control-label' do
- %strong Project name
+ = f.label :path, class: 'control-label' do
+ %strong Project path
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control", tabindex: 1, autofocus: true
+ .input-group
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true
+ .input-group-addon
+ \.git
- if current_user.can_select_namespace?
.form-group
@@ -18,40 +21,71 @@
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
%hr
- .js-toggle-container
- .form-group
- .col-sm-2
- .col-sm-10
- = link_to "#", class: 'js-toggle-button' do
- %i.icon-edit
- %span Customize repository name?
- .js-toggle-content.hide
- .form-group
- = f.label :path, class: 'control-label' do
- %span Repository name
- .col-sm-10
- .input-group
- = f.text_field :path, class: 'form-control'
- %span.input-group-addon .git
- .js-toggle-container
+ .project-import.js-toggle-container
.form-group
- .col-sm-2
+ %label.control-label Import project from
.col-sm-10
- = link_to "#", class: 'js-toggle-button' do
- %i.icon-upload-alt
- %span Import existing repository?
+ - if github_import_enabled?
+ = link_to status_import_github_path, class: 'btn' do
+ %i.fa.fa-github
+ GitHub
+ - else
+ = link_to '#', class: 'how_to_import_link light btn' do
+ %i.fa.fa-github
+ GitHub
+ = render 'github_import_modal'
+
+
+ - if bitbucket_import_enabled?
+ = link_to status_import_bitbucket_path, class: 'btn' do
+ %i.fa.fa-bitbucket
+ Bitbucket
+ - else
+ = link_to '#', class: 'how_to_import_link light btn' do
+ %i.fa.fa-bitbucket
+ Bitbucket
+ = render 'bitbucket_import_modal'
+
+ - unless request.host == 'gitlab.com'
+ - if gitlab_import_enabled?
+ = link_to status_import_gitlab_path, class: 'btn' do
+ %i.fa.fa-heart
+ GitLab.com
+ - else
+ = link_to '#', class: 'how_to_import_link light btn' do
+ %i.fa.fa-heart
+ GitLab.com
+ = render 'gitlab_import_modal'
+
+ = link_to new_import_gitorious_path, class: 'btn' do
+ %i.icon-gitorious.icon-gitorious-small
+ Gitorious.org
+
+ = link_to new_import_google_code_path, class: 'btn' do
+ %i.fa.fa-google
+ Google Code
+
+ = link_to "#", class: 'btn js-toggle-button' do
+ %i.fa.fa-git
+ %span Any repo by URL
+
.js-toggle-content.hide
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
- %span Import existing repo
+ %span Git repository URL
.col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
- .bs-callout.bs-callout-info
- This url must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
- %br
- The import will time out after 2 minutes. For big repositories, use a clone/push combination.
- %hr
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
+ .well.prepend-top-20
+ %ul
+ %li
+ The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git.
+ %li
+ The import will time out after 4 minutes. For big repositories, use a clone/push combination.
+ %li
+ To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}.
+
+ %hr.prepend-botton-10
.form-group
= f.label :description, class: 'control-label' do
@@ -68,12 +102,19 @@
.pull-right
.light
Need a group for several dependent projects?
- = link_to new_group_path, class: "btn btn-tiny" do
+ = link_to new_group_path, class: "btn btn-xs" do
Create a group
.save-project-loader.hide
.center
%h2
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Creating project & repository.
%p Please wait a moment, this page will automatically refresh when ready.
+
+:coffeescript
+ $('.how_to_import_link').bind 'click', (e) ->
+ e.preventDefault()
+ import_modal = $(this).next(".modal").show()
+ $('.modal-header .close').bind 'click', ->
+ $(".modal").hide()
diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml
deleted file mode 100644
index 9ecbbe7508..0000000000
--- a/app/views/projects/new_tree/show.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-%h3.page-title New file
-%hr
-.file-editor
- = form_tag(project_new_tree_path(@project, @id), method: :put, class: "form-horizontal") do
- .form-group.commit_message-group
- = label_tag 'file_name', class: "control-label" do
- File name
- .col-sm-10
- .input-group
- %span.input-group-addon
- = @path[-1] == "/" ? @path : @path + "/"
- = text_field_tag 'file_name', params[:file_name], placeholder: "sample.rb", required: true, class: 'form-control'
- %span.input-group-addon
- on
- %span= @ref
-
- .form-group.commit_message-group
- = label_tag :encoding, class: "control-label" do
- Encoding
- .col-sm-10
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
-
- .form-group.commit_message-group
- = label_tag 'commit_message', class: "control-label" do
- Commit message
- .col-sm-10
- = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message',
- params[:commit_message], placeholder: "Added new file", required: true, rows: 3, class: 'form-control')}
-
- .file-holder
- .file-title
- %i.icon-file
- .file-content.code
- %pre#editor= params[:content]
-
- .form-actions
- = hidden_field_tag 'content', '', id: "file-content"
- .commit-button-annotation
- = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create'
- .message
- to branch
- %strong= @ref
- = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message}
-
-:javascript
- ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
- var editor = ace.edit("editor");
-
- disableButtonIfEmptyField("#commit_message", ".js-commit-button");
-
- $(".js-commit-button").click(function(){
- $("#file-content").val(editor.getValue());
- $(".file-editor form").submit();
- });
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
new file mode 100644
index 0000000000..720957e833
--- /dev/null
+++ b/app/views/projects/no_repo.html.haml
@@ -0,0 +1,22 @@
+%h2
+ %i.fa.fa-warning
+ No repository
+
+%p.slead
+ The repository for this project does not exist.
+ %br
+ This means you can not push code until you create an empty repository or import existing one.
+%hr
+
+.no-repo-actions
+ = link_to namespace_project_repository_path(@project.namespace, @project), method: :post, class: 'btn btn-primary' do
+ Create empty bare repository
+
+ %strong.prepend-left-10.append-right-10 or
+
+ = link_to new_namespace_project_import_path(@project.namespace, @project), class: 'btn' do
+ Import repository
+
+- if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/notes/_diff_note_link.html.haml b/app/views/projects/notes/_diff_note_link.html.haml
deleted file mode 100644
index 377c926a20..0000000000
--- a/app/views/projects/notes/_diff_note_link.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-- note = @project.notes.new(@comments_target.merge({ line_code: line_code }))
-= link_to "",
- "javascript:;",
- class: "add-diff-note js-add-diff-note-button",
- data: { noteable_type: note.noteable_type,
- noteable_id: note.noteable_id,
- commit_id: note.commit_id,
- line_code: note.line_code,
- discussion_id: note.discussion_id },
- title: "Add a comment to this line"
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index 79a66eff12..c731baf0a6 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -3,8 +3,8 @@
- if !defined?(line) || line == note.diff_line
%tr.notes_holder
%td.notes_line{ colspan: 2 }
- %span.btn.disabled
- %i.icon-comment
+ %span.discussion-notes-count
+ %i.fa.fa-comment
= notes.count
%td.notes_content
%ul.notes{ rel: note.discussion_id }
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index 8adf903a9a..789f3e19fd 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -1,14 +1,13 @@
-- note1 = notes1.first # example note
-- note2 = notes2.first # example note
--# Check if line want not changed since comment was left
-/- if !defined?(line) || line == note.diff_line
+- note1 = notes1.present? ? notes1.first : nil
+- note2 = notes2.present? ? notes2.first : nil
+
%tr.notes_holder
- if note1
%td.notes_line
%span.btn.disabled
- %i.icon-comment
+ %i.fa.fa-comment
= notes1.count
- %td.notes_content
+ %td.notes_content.parallel
%ul.notes{ rel: note1.discussion_id }
= render notes1
@@ -21,9 +20,9 @@
- if note2
%td.notes_line
%span.btn.disabled
- %i.icon-comment
+ %i.fa.fa-comment
= notes2.count
- %td.notes_content
+ %td.notes_content.parallel
%ul.notes{ rel: note2.discussion_id }
= render notes2
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
index 8c7964cbf3..b8068835b3 100644
--- a/app/views/projects/notes/_discussion.html.haml
+++ b/app/views/projects/notes/_discussion.html.haml
@@ -1,8 +1,13 @@
- note = discussion_notes.first
-- if note.for_merge_request?
- - if note.outdated?
- = render "projects/notes/discussions/outdated", discussion_notes: discussion_notes
- - else
- = render "projects/notes/discussions/active", discussion_notes: discussion_notes
-- else
- = render "projects/notes/discussions/commit", discussion_notes: discussion_notes
+.timeline-entry
+ .timeline-entry-inner
+ .timeline-icon
+ = link_to user_path(note.author) do
+ = image_tag avatar_icon(note.author_email), class: "avatar s40"
+ .timeline-content
+ - if note.for_merge_request?
+ - (active_notes, outdated_notes) = discussion_notes.partition(&:active?)
+ = render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0
+ = render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0
+ - else
+ = render "projects/notes/discussions/commit", discussion_notes: discussion_notes
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
new file mode 100644
index 0000000000..acb3991d29
--- /dev/null
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -0,0 +1,15 @@
+.note-edit-form
+ = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f|
+ = note_target_fields(note)
+ = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do
+ = render 'projects/zen', f: f, attr: :note,
+ classes: 'note_text js-note-text'
+
+ .comment-hints.clearfix
+ .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
+ .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
+
+ .note-form-actions
+ .buttons
+ = f.submit 'Save Comment', class: "btn btn-primary btn-save btn-grouped js-comment-button"
+ = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel"
\ No newline at end of file
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 5ebafb13f1..2ada6cb670 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,27 +1,18 @@
-= form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" }, authenticity_token: true do |f|
- = note_target_fields
+= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f|
+ = note_target_fields(@note)
= f.hidden_field :commit_id
= f.hidden_field :line_code
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
- %ul.nav.nav-tabs
- %li.active
- = link_to '#note-write-holder', class: 'js-note-write-button' do
- Write
- %li
- = link_to '#note-preview-holder', class: 'js-note-preview-button', data: { url: preview_project_notes_path(@project) } do
- Preview
- %div
- .note-write-holder
- = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input markdown-area'
+ = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do
+ = render 'projects/zen', f: f, attr: :note,
+ classes: 'note_text js-note-text'
- .light.clearfix
- .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
-
- .note-preview-holder.hide
- .js-note-preview
+ .comment-hints.clearfix
+ .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
+ .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
+ .error-alert
.note-form-actions
.buttons
@@ -29,13 +20,5 @@
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
- .note-form-option
- %a.choose-btn.btn.js-choose-note-attachment-button
- %i.icon-paper-clip
- %span Choose File ...
-
- %span.file_name.js-attachment-filename File name...
- = f.file_field :attachment, class: "js-note-attachment-input hidden"
-
:javascript
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 5e84aed0cc..0728f8fa42 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,66 +1,71 @@
-%li{ id: dom_id(note), class: dom_class(note), data: { discussion: note.discussion_id } }
- .note-header
- .note-actions
- = link_to "##{dom_id(note)}", name: dom_id(note) do
- %i.icon-link
- Link here
-
- - if(note.author_id == current_user.try(:id)) || can?(current_user, :admin_note, @project)
- = link_to "#", title: "Edit comment", class: "js-note-edit" do
- %i.icon-edit
- Edit
-
- = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do
- %i.icon-trash.cred
- Remove
- = image_tag avatar_icon(note.author_email), class: "avatar s32"
- = link_to_member(@project, note.author, avatar: false)
- %span.note-last-update
- = note_timestamp(note)
-
- - if note.upvote?
- %span.vote.upvote.label.label-success
- %i.icon-thumbs-up
- \+1
- - if note.downvote?
- %span.vote.downvote.label.label-danger
- %i.icon-thumbs-down
- \-1
-
-
- .note-body
- .note-text
- = preserve do
- = markdown(note.note, {no_header_anchors: true})
-
- .note-edit-form
- = form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f|
- = f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on'
-
- .form-actions.clearfix
- = f.submit 'Save changes', class: "btn btn-primary btn-save js-comment-button"
-
- .note-form-option
- %a.choose-btn.btn.js-choose-note-attachment-button
- %i.icon-paper-clip
- %span Choose File ...
+%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
+ .timeline-entry-inner
+ .timeline-icon
+ - if note.system
+ %span.fa.fa-circle
+ - else
+ = link_to user_path(note.author) do
+ = image_tag avatar_icon(note.author_email), class: "avatar s40", alt: ''
+ .timeline-content
+ .note-header
+ .note-actions
+ = link_to "##{dom_id(note)}", name: dom_id(note) do
+ %i.fa.fa-link
+ Link here
+
+ - if can?(current_user, :admin_note, note) && note.editable?
+ = link_to "#", title: "Edit comment", class: "js-note-edit" do
+ %i.fa.fa-pencil-square-o
+ Edit
- %span.file_name.js-attachment-filename File name...
- = f.file_field :attachment, class: "js-note-attachment-input hidden"
+ = link_to namespace_project_note_path(@project.namespace, @project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do
+ %i.fa.fa-trash-o.cred
+ Remove
+ - if note.system
+ = link_to user_path(note.author) do
+ = image_tag avatar_icon(note.author_email), class: "avatar s16", alt: ''
+ = link_to_member(@project, note.author, avatar: false)
+ %span.author-username
+ = '@' + note.author.username
+ %span.note-last-update
+ = note_timestamp(note)
- = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel"
+ - if note.superceded?(@notes)
+ - if note.upvote?
+ %span.vote.upvote.label.label-gray.strikethrough
+ %i.fa.fa-thumbs-up
+ \+1
+ - if note.downvote?
+ %span.vote.downvote.label.label-gray.strikethrough
+ %i.fa.fa-thumbs-down
+ \-1
+ - else
+ - if note.upvote?
+ %span.vote.upvote.label.label-success
+ %i.fa.fa-thumbs-up
+ \+1
+ - if note.downvote?
+ %span.vote.downvote.label.label-danger
+ %i.fa.fa-thumbs-down
+ \-1
- - if note.attachment.url
- .note-attachment
- - if note.attachment.image?
- = link_to note.attachment.secure_url, target: '_blank' do
- = image_tag note.attachment.secure_url, class: 'note-image-attach'
- .attachment.pull-right
- = link_to note.attachment.secure_url, target: "_blank" do
- %i.icon-paper-clip
- = note.attachment_identifier
- = link_to delete_attachment_project_note_path(@project, note),
- title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do
- %i.icon-trash.cred
- .clear
+ .note-body
+ .note-text
+ = preserve do
+ = markdown(note.note, {no_header_anchors: true})
+ = render 'projects/notes/edit_form', note: note
+
+ - if note.attachment.url
+ .note-attachment
+ - if note.attachment.image?
+ = link_to note.attachment.url, target: '_blank' do
+ = image_tag note.attachment.url, class: 'note-image-attach'
+ .attachment
+ = link_to note.attachment.url, target: "_blank" do
+ %i.fa.fa-paperclip
+ = note.attachment_identifier
+ = link_to delete_attachment_namespace_project_note_path(@project.namespace, @project, note),
+ title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do
+ %i.fa.fa-trash-o.cred
+ .clear
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 052661962e..813e37276b 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -1,4 +1,4 @@
-%ul#notes-list.notes.main-notes-list
+%ul#notes-list.notes.main-notes-list.timeline
= render "projects/notes/notes"
.js-notes-busy
@@ -7,4 +7,4 @@
= render "projects/notes/form"
:javascript
- new Notes("#{project_notes_path(target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i})
+ new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i})
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index ef296b35dd..7c6f724317 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -3,13 +3,12 @@
.discussion-header
.discussion-actions
= link_to "#", class: "js-toggle-button" do
- %i.icon-chevron-up
+ %i.fa.fa-chevron-up
Show/hide discussion
- = image_tag avatar_icon(note.author_email), class: "avatar s32"
%div
= link_to_member(@project, note.author, avatar: false)
started a discussion
- = link_to diffs_project_merge_request_path(note.project, note.noteable, anchor: note.line_code) do
+ = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
%strong on the diff
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 78460974a9..62609cfc1c 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -3,13 +3,12 @@
.discussion-header
.discussion-actions
= link_to "#", class: "js-toggle-button" do
- %i.icon-chevron-up
+ %i.fa.fa-chevron-up
Show/hide discussion
- = image_tag avatar_icon(note.author_email), class: "avatar s32"
%div
= link_to_member(@project, note.author, avatar: false)
started a discussion on commit
- = link_to(note.noteable.short_id, project_commit_path(note.project, note.noteable), class: 'monospace')
+ = link_to(note.noteable.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
index 26c5494f46..711aa39101 100644
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ b/app/views/projects/notes/discussions/_diff.html.haml
@@ -2,25 +2,28 @@
- if diff
.diff-file
.diff-header
- - if diff.deleted_file
- %span= diff.old_path
- - else
- %span= diff.new_path
- - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
- %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
- %br/
+ %span
+ - if diff.deleted_file
+ = diff.old_path
+ - else
+ = diff.new_path
+ - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
+ %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
.diff-content
%table
- - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old|
+ - note.truncated_diff_lines.each do |line|
+ - line_code = generate_line_code(note.file_path, line)
%tr.line_holder{ id: line_code }
- - if type == "match"
+ - if line.type == "match"
%td.old_line= "..."
%td.new_line= "..."
- %td.line_content.matched= line
+ %td.line_content.matched= line.text
- else
- %td.old_line= raw(type == "new" ? " " : line_old)
- %td.new_line= raw(type == "old" ? " " : line_new)
- %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} "
+ %td.old_line{class: line.type == "new" ? "new" : "old"}
+ = raw(line.type == "new" ? " " : line.old_pos)
+ %td.new_line{class: line.type == "new" ? "new" : "old"}
+ = raw(line.type == "old" ? " " : line.new_pos)
+ %td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index 67c29be8ac..52a1d342f5 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -3,9 +3,8 @@
.discussion-header
.discussion-actions
= link_to "#", class: "js-toggle-button" do
- %i.icon-chevron-down
+ %i.fa.fa-chevron-down
Show/hide discussion
- = image_tag avatar_icon(note.author_email), class: "avatar s32"
%div
= link_to_member(@project, note.author, avatar: false)
started a discussion on the
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
new file mode 100644
index 0000000000..43e92437cf
--- /dev/null
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -0,0 +1,16 @@
+.panel.panel-default
+ .panel-heading
+ %strong #{@group.name}
+ group members
+ %small
+ (#{members.count})
+ .panel-head-actions
+ = link_to group_group_members_path(@group), class: 'btn btn-sm' do
+ %i.fa.fa-pencil-square-o
+ Edit group members
+ %ul.well-list
+ - members.each do |member|
+ = render 'groups/group_members/group_member', member: member, show_controls: false
+ - if members.count > 20
+ %li
+ and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)}
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
new file mode 100644
index 0000000000..d708b01a11
--- /dev/null
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -0,0 +1,18 @@
+= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f|
+ .form-group
+ = f.label :user_ids, "People", class: 'control-label'
+ .col-sm-10
+ = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
+ .help-block
+ Search for existing users or invite new ones using their email address.
+
+ .form-group
+ = f.label :access_level, "Project Access", class: 'control-label'
+ .col-sm-10
+ = select_tag :access_level, options_for_select(ProjectMember.access_roles, @project_member.access_level), class: "project-access-select select2"
+ .help-block
+ Read more about role permissions
+ %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+
+ .form-actions
+ = f.submit 'Add users to project', class: "btn btn-create"
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
new file mode 100644
index 0000000000..635e4d7094
--- /dev/null
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -0,0 +1,53 @@
+- user = member.user
+- return unless user || member.invite?
+
+%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
+ %span.list-item-name
+ - if member.user
+ = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: ''
+ %strong
+ = link_to user.name, user_path(user)
+ %span.cgray= user.username
+ - if user == current_user
+ %span.label.label-success It's you
+ - if user.blocked?
+ %label.label.label-danger
+ %strong Blocked
+ - else
+ = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: ''
+ %strong
+ = member.invite_email
+ %span.cgray
+ invited
+ - if member.created_by
+ by
+ = link_to member.created_by.name, user_path(member.created_by)
+ = time_ago_with_tooltip(member.created_at)
+
+ - if current_user_can_admin_project
+ = link_to resend_invite_namespace_project_project_member_path(@project.namespace, @project, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
+ Resend invite
+
+ - if current_user_can_admin_project
+ - unless @project.personal? && user == current_user
+ .pull-right
+ %strong= member.human_access
+ = button_tag class: "btn-xs btn js-toggle-button",
+ title: 'Edit access level', type: 'button' do
+ %i.fa.fa-pencil-square-o
+
+
+ - if current_user == user
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: "Leave project?"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do
+ %i.fa.fa-minus.fa-inverse
+ - else
+ = link_to namespace_project_project_member_path(@project.namespace, @project, member), data: { confirm: remove_from_project_team_message(@project, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do
+ %i.fa.fa-minus.fa-inverse
+
+ .edit-member.hide.js-toggle-content
+ %br
+ = form_for member, as: :project_member, url: namespace_project_project_member_path(@project.namespace, @project, member), remote: true do |f|
+ .prepend-top-10
+ = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: 'form-control'
+ .prepend-top-10
+ = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
new file mode 100644
index 0000000000..615c425e59
--- /dev/null
+++ b/app/views/projects/project_members/_team.html.haml
@@ -0,0 +1,11 @@
+- can_admin_project = can?(current_user, :admin_project, @project)
+
+.panel.panel-default.prepend-top-20
+ .panel-heading
+ %strong #{@project.name}
+ project members
+ %small
+ (#{members.count})
+ %ul.well-list
+ - members.each do |project_member|
+ = render 'project_member', member: project_member, current_user_can_admin_project: can_admin_project
diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/project_members/import.html.haml
similarity index 53%
rename from app/views/projects/team_members/import.html.haml
rename to app/views/projects/project_members/import.html.haml
index d3e4a76201..293754cd0c 100644
--- a/app/views/projects/team_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -1,14 +1,14 @@
%h3.page-title
- = "Import members from another project"
+ Import members from another project
%p.light
Only project members will be imported. Group members will be skipped.
%hr
-= form_tag apply_import_project_team_members_path(@project), method: 'post', class: 'form-horizontal' do
+= form_tag apply_import_namespace_project_project_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do
.form-group
= label_tag :source_project_id, "Project", class: 'control-label'
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
- = submit_tag 'Import project members', class: "btn btn-create"
- = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
+ = button_tag 'Import project members', class: "btn btn-create"
+ = link_to "Cancel", namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
new file mode 100644
index 0000000000..36a6f6a155
--- /dev/null
+++ b/app/views/projects/project_members/index.html.haml
@@ -0,0 +1,35 @@
+%h3.page-title
+ Users with access to this project
+
+%p.light
+ Read more about project permissions
+ %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+
+%hr
+
+.clearfix.js-toggle-container
+ = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
+ = button_tag 'Search', class: 'btn'
+
+ - if can?(current_user, :admin_project_member, @project)
+ %span.pull-right
+ = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do
+ Add members
+ %i.fa.fa-chevron-down
+ = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
+ Import members
+
+ .js-toggle-content.hide.new-group-member-holder
+ = render "new_project_member"
+
+= render "team", members: @project_members
+
+- if @group
+ = render "group_members", members: @group_members
+
+:coffeescript
+ $('form.member-search-form').on 'submit', (event) ->
+ event.preventDefault()
+ Turbolinks.visit @.action + '?' + $(@).serialize()
diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml
new file mode 100644
index 0000000000..811b185882
--- /dev/null
+++ b/app/views/projects/project_members/update.js.haml
@@ -0,0 +1,3 @@
+- can_admin_project = can?(current_user, :admin_project, @project)
+:plain
+ $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member, current_user_can_admin_project: can_admin_project))}');
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
new file mode 100644
index 0000000000..bb49f4de87
--- /dev/null
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -0,0 +1,34 @@
+- unless @branches.empty?
+ %br
+ %h4 Already Protected:
+ %table.table.protected-branches-list
+ %thead
+ %tr.no-border
+ %th Branch
+ %th Developers can push
+ %th Last commit
+ %th
+
+ %tbody
+ - @branches.each do |branch|
+ - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
+ %tr
+ %td
+ = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
+ %strong= branch.name
+ - if @project.root_ref?(branch.name)
+ %span.label.label-info default
+ %td
+ = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
+ %td
+ - if commit = branch.commit
+ = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
+ = commit.short_id
+ ·
+ #{time_ago_with_tooltip(commit.committed_date)}
+ - else
+ (branch was removed from repository)
+ %td
+ .pull-right
+ - if can? current_user, :admin_project, @project
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 7925e80030..a3464c0e5e 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -1,17 +1,17 @@
%h3.page-title Protected branches
-%p.light This ability allows to keep stable branches secured and force code review before merge to protected branches
+%p.light Keep stable branches secure and force developers to use Merge Requests
%hr
-.bs-callout.bs-callout-info
- %p Protected branches designed to
+.well.append-bottom-20
+ %p Protected branches are designed to
%ul
- %li prevent push for all except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
- %li prevent branch from force push
- %li prevent branch from removal
- %p Read more about project permissions #{link_to "here", help_page_path("permissions", "permissions"), class: "underlined-link"}
+ %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
+ %li prevent anyone from force pushing to the branch
+ %li prevent anyone from deleting the branch
+ %p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
- if can? current_user, :admin_project, @project
- = form_for [@project, @protected_branch], html: { class: 'form-horizontal' } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f|
-if @protected_branch.errors.any?
.alert.alert-danger
%ul
@@ -22,29 +22,14 @@
= f.label :name, "Branch", class: 'control-label'
.col-sm-10
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :developers_can_push do
+ = f.check_box :developers_can_push
+ %strong Developers can push
+ .help-block Allow developers to push to this branch
.form-actions
= f.submit 'Protect', class: "btn-create btn"
-- unless @branches.empty?
- %h5 Already Protected:
- %ul.bordered-list.protected-branches-list
- - @branches.each do |branch|
- %li
- %h4
- = link_to project_commits_path(@project, branch.name) do
- %strong= branch.name
- - if @project.root_ref?(branch.name)
- %span.label.label-info default
- %span.label.label-success
- %i.icon-lock
- .pull-right
- - if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
+= render 'branches_list'
- - if commit = branch.commit
- = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- %span.light
- = gfm escape_once(truncate(commit.title, length: 40))
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
index 948a21aa81..35c15cf3a9 100644
--- a/app/views/projects/refs/logs_tree.js.haml
+++ b/app/views/projects/refs/logs_tree.js.haml
@@ -11,9 +11,9 @@
- if @logs.present?
:plain
var current_url = location.href.replace(/\/?$/, '/');
- var log_url = '#{project_tree_url(@project, tree_join(@ref, @path || '/'))}'.replace(/\/?$/, '/');
+ var log_url = '#{namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))}'.replace(/\/?$/, '/');
if(current_url == log_url) {
// Load 10 more commit log for each file in tree
// if we still on the same page
- ajaxGet('#{logs_file_project_ref_path(@project, @ref, @path || '/', offset: (@offset + @limit))}');
+ ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))}');
}
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index 0bf59a2038..b9486a9b49 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -1,37 +1,37 @@
- ref = ref || nil
- btn_class = btn_class || ''
-- split_button = split_button || false
+- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
- %i.icon-download-alt
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
+ %i.fa.fa-download
%span Download zip
- %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
+ %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
- %ul.dropdown-menu{ role: 'menu' }
+ %ul.col-xs-10.dropdown-menu{ role: 'menu' }
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do
- %i.icon-download-alt
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
+ %i.fa.fa-download
%span Download zip
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
- %i.icon-download-alt
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
+ %i.fa.fa-download
%span Download tar.gz
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
- %i.icon-download-alt
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
+ %i.fa.fa-download
%span Download tar.bz2
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do
- %i.icon-download-alt
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do
+ %i.fa.fa-download
%span Download tar
- else
%span.btn-group{class: btn_class}
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
- %i.icon-download-alt
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ %i.fa.fa-download
%span zip
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
- %i.icon-download-alt
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
+ %i.fa.fa-download
%span tar.gz
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index c77ffff43f..f3526ad074 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -1,7 +1,7 @@
- commit = update
%tr
%td
- = link_to project_commits_path(@project, commit.head.name) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, commit.head.name) do
%strong
= commit.head.name
- if @project.root_ref?(commit.head.name)
@@ -9,7 +9,7 @@
%td
%div
- = link_to project_commits_path(@project, commit.id) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
= gfm escape_once(truncate(commit.title, length: 40))
diff --git a/app/views/projects/repositories/stats.html.haml b/app/views/projects/repositories/stats.html.haml
deleted file mode 100644
index 70db27d644..0000000000
--- a/app/views/projects/repositories/stats.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-= render "projects/commits/head"
-.row
- .col-md-6
- %div#activity-chart.chart
- %hr
- %p
- %b Total commits:
- %span= @repository.commit_count
- %p
- %b Total files in #{@repository.root_ref}:
- %span= @stats.files_count
- %p
- %b Authors:
- %span= @stats.authors_count
-
-
- .col-md-6
- %h4 Top 50 Committers:
- %ol.styled
- - @stats.authors[0...50].each do |author|
- %li
- = image_tag avatar_icon(author.email, 16), class: 'avatar s16', alt: ''
- = author.name
- %small.light= author.email
- .pull-right
- = author.commits
-
-
-:javascript
- var labels = [#{@graph.labels.to_json}];
- var commits = [#{@graph.commits.join(', ')}];
- var title = "Commit activity for last #{@graph.weeks} weeks";
- Chart.init(labels, commits, title);
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index a5db7969a6..ce6b7a0737 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -5,12 +5,12 @@
%p= @service.description
.back-link
- = link_to project_services_path(@project) do
+ = link_to namespace_project_services_path(@project.namespace, @project) do
← to services
%hr
-= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
+= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
- if @service.errors.any?
.alert.alert-danger
%ul
@@ -18,21 +18,72 @@
%li= msg
- if @service.help.present?
- .bs-callout
- = @service.help
+ .well
+ = preserve do
+ = markdown @service.help
.form-group
= f.label :active, "Active", class: "control-label"
.col-sm-10
= f.check_box :active
+ - if @service.supported_events.length > 1
+ .form-group
+ = f.label :url, "Trigger", class: 'control-label'
+ .col-sm-10
+ - if @service.supported_events.include?("push")
+ %div
+ = f.check_box :push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :push_events, class: 'list-label' do
+ %strong Push events
+ %p.light
+ This url will be triggered by a push to the repository
+ - if @service.supported_events.include?("tag_push")
+ %div
+ = f.check_box :tag_push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :tag_push_events, class: 'list-label' do
+ %strong Tag push events
+ %p.light
+ This url will be triggered when a new tag is pushed to the repository
+ - if @service.supported_events.include?("note")
+ %div
+ = f.check_box :note_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :note_events, class: 'list-label' do
+ %strong Comments
+ %p.light
+ This url will be triggered when someone adds a comment
+ - if @service.supported_events.include?("issue")
+ %div
+ = f.check_box :issues_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :issues_events, class: 'list-label' do
+ %strong Issues events
+ %p.light
+ This url will be triggered when an issue is created
+ - if @service.supported_events.include?("merge_request")
+ %div
+ = f.check_box :merge_requests_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :merge_requests_events, class: 'list-label' do
+ %strong Merge Request events
+ %p.light
+ This url will be triggered when a merge request is created
+
- @service.fields.each do |field|
- name = field[:name]
+ - title = field[:title] || name.humanize
+ - value = service_field_value(field[:type], @service.send(name))
- type = field[:type]
- placeholder = field[:placeholder]
+ - choices = field[:choices]
+ - default_choice = field[:default_choice]
+ - help = field[:help]
.form-group
- = f.label name, class: "control-label"
+ = f.label name, title, class: "control-label"
.col-sm-10
- if type == 'text'
= f.text_field name, class: "form-control", placeholder: placeholder
@@ -40,9 +91,16 @@
= f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
- elsif type == 'checkbox'
= f.check_box name
+ - elsif type == 'select'
+ = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
+ - elsif type == 'password'
+ = f.password_field name, placeholder: value, class: 'form-control'
+ - if help
+ %span.help-block= help
.form-actions
= f.submit 'Save', class: 'btn btn-save'
- - if @service.valid? && @service.activated? && @service.can_test?
- = link_to 'Test settings', test_project_service_path(@project, @service.to_param), class: 'btn'
+ - if @service.valid? && @service.activated?
+ - disabled = @service.can_test? ? '':'disabled'
+ = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml
index 7271dd830c..0d3ccb6bb8 100644
--- a/app/views/projects/services/index.html.haml
+++ b/app/views/projects/services/index.html.haml
@@ -1,13 +1,22 @@
%h3.page-title Project services
%p.light Project services allow you to integrate GitLab with other applications
-%hr
-%ul.bordered-list
+%table.table
+ %thead
+ %tr
+ %th
+ %th Service
+ %th Description
+ %th Last edit
- @services.sort_by(&:title).each do |service|
- %li
- %h4
- = link_to edit_project_service_path(@project, service.to_param) do
- = service.title
- .pull-right
- = boolean_to_icon service.activated?
- %p= service.description
+ %tr
+ %td
+ = boolean_to_icon service.activated?
+ %td
+ = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 0fd290b539..4464c51744 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,82 +1,108 @@
+- if current_user && can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+ = render 'shared/no_password'
+
= render "home_panel"
-.row
- %section.col-md-9
- = render "events/event_last_push", event: @last_push
- = render 'shared/event_filter'
- .content_list
- = spinner
- %aside.col-md-3.project-side.hidden-sm.hidden-xs
- .clearfix
- - if @project.archived?
- .alert.alert-warning
- %h4
- %i.icon-warning-sign
- Archived project!
- %p Repository is read-only
+%ul.nav.nav-tabs
+ %li.active
+ = link_to '#tab-activity', 'data-toggle' => 'tab' do
+ Activity
+ - if @repository.readme
+ %li
+ = link_to '#tab-readme', 'data-toggle' => 'tab' do
+ Readme
+ - if @repository.changelog
+ %li
+ = link_to changelog_url(@project) do
+ Changelog
+ - if @repository.contribution_guide
+ %li
+ = link_to contribution_guide_url(@project) do
+ Contribution guide
+ - if @repository.license
+ %li
+ = link_to license_url(@project) do
+ License
- - if @project.forked_from_project
- .alert.alert-success
- %i.icon-code-fork.project-fork-icon
- Forked from:
- %br
- = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
+ .project-home-links
+ - unless @project.empty_repo?
+ = link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref)
+ = link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), namespace_project_branches_path(@project.namespace, @project)
+ = link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), namespace_project_tags_path(@project.namespace, @project)
+ %span.light.prepend-left-20= repository_size
- .star-buttons
- %span.star.js-toggler-container{class: @show_star ? 'on' : ''}
- - if current_user
- = link_to_toggle_star('Star this project.', false, true)
- = link_to_toggle_star('Unstar this project.', true, true)
- - else
- = link_to_toggle_star('You must sign in to star a project.', false, false)
+.tab-content
+ .tab-pane.active#tab-activity
+ .row
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
+ %section.col-md-9
+ = render "events/event_last_push", event: @last_push
+ = render 'shared/event_filter'
+ .content_list
+ = spinner
+ %aside.col-md-3.project-side
+ .clearfix
+ - if @project.archived?
+ .alert.alert-warning
+ %h4
+ %i.fa.fa-exclamation-triangle
+ Archived project!
+ %p Repository is read-only
- - unless @project.empty_repo?
- .fork-buttons
- - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- - if current_user.already_forked?(@project)
- = link_to project_path(current_user.fork_of(@project)), class: 'btn btn-block' do
- %i.icon-compass
- Go to fork
+ - if @project.forked_from_project
+ .well
+ %i.fa.fa-code-fork.project-fork-icon
+ Forked from:
+ %br
+ = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
+
+ - unless @project.empty_repo?
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do
+ %i.fa.fa-exchange
+ Compare code
+
+ - if can?(current_user, :download_code, @project)
+ = render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-block'
+
+ - if version = @repository.version
+ - detail_url = changelog_url(@project) || version_url(@project)
+ = link_to detail_url, class: 'btn btn-block' do
+ %i.fa.fa-file-text-o
+ Version:
%span.count
- = @project.forks_count
+ = @repository.blob_by_oid(version.id).data
+
+ .prepend-top-10.append-bottom-10
+ %p
+ %span.light Created on
+ #{@project.created_at.stamp('Aug 22, 2013')}
+ %p
+ %span.light Owned by #{@project.group ? "the" : nil}
+ - if @project.group
+ #{link_to @project.group.name, @project.group} group
- else
- = link_to fork_project_path(@project), title: "Fork", class: "btn btn-block", method: "POST" do
- %i.icon-code-fork
- Fork repository
- %span.count
- = @project.forks_count
- - unless @project.empty_repo?
- - if can? current_user, :download_code, @project
- = render 'projects/repositories/download_archive', btn_class: 'btn-block btn-group-justified', split_button: true
+ #{link_to @project.owner_name, @project.owner}
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do
- Compare code
- - if @repository.readme
- - readme = @repository.readme
- = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)), class: 'btn btn-block' do
+ .prepend-top-10
+ - @project.ci_services.each do |ci_service|
+ - if ci_service.active? && ci_service.respond_to?(:builds_path)
+ %hr
+ - if ci_service.respond_to?(:status_img_path)
+ = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do
+ = image_tag ci_service.status_img_path, alt: "build status"
+ - else
+ %span.light CI provided by
+ = link_to ci_service.title, ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink'
+
+ - if readme = @repository.readme
+ .tab-pane#tab-readme
+ %article.readme-holder#README
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
+ %h4.readme-file-title
+ %i.fa.fa-file
= readme.name
-
- - if @repository.version
- - version = @repository.version
- = link_to project_blob_path(@project, tree_join(@repository.root_ref, version.name)), class: 'btn btn-block' do
- Version:
- %span.count
- = @repository.blob_by_oid(version.id).data
-
- .prepend-top-10
- %p
- %span.light Created on
- #{@project.created_at.stamp('Aug 22, 2013')}
- %p
- %span.light Owned by
- - if @project.group
- #{link_to @project.group.name, @project.group} group
- - else
- #{link_to @project.owner_name, @project.owner}
-
-
- - if @project.gitlab_ci?
- %hr
- = link_to @project.gitlab_ci_service.builds_path do
- = image_tag @project.gitlab_ci_service.status_img_path, alt: "build status"
+ .wiki
+ = render_readme(readme)
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index f6a5bf9e4f..2d4d5d030a 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -1,4 +1,4 @@
%h3.page-title
Edit snippet
%hr
-= render "shared/snippets/form", url: project_snippet_path(@project, @snippet)
+= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index e60f9a4432..e2d8ec673a 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
Snippets
- if can? current_user, :write_project_snippet, @project
- = link_to new_project_snippet_path(@project), class: "btn btn-new pull-right", title: "New Snippet" do
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Snippet" do
Add new snippet
%p.light
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 10f684b631..bb659dba0c 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -1,4 +1,4 @@
%h3.page-title
New snippet
%hr
-= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
+= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index e4fdbd868c..d19689a105 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -2,7 +2,7 @@
= @snippet.title
.pull-right
- = link_to new_project_snippet_path(@project), class: "btn btn-new", title: "New Snippet" do
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
Add new snippet
%hr
@@ -17,21 +17,21 @@
= @snippet.author_name
.back-link
- = link_to project_snippets_path(@project) do
+ = link_to namespace_project_snippets_path(@project.namespace, @project) do
← project snippets
.file-holder
.file-title
- %i.icon-file
- %span.file_name
+ %i.fa.fa-file
+ %strong
= @snippet.file_name
- .options
+ .file-actions
.btn-group
- if can?(current_user, :modify_project_snippet, @snippet)
- = link_to "edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small", title: 'Edit Snippet'
- = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-small", target: "_blank"
+ = link_to "edit", edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", title: 'Edit Snippet'
+ = link_to "raw", raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
- if can?(current_user, :admin_project_snippet, @snippet)
- = link_to "remove", project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-small btn-remove", title: 'Delete Snippet'
+ = link_to "remove", namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet'
= render 'shared/snippets/blob'
%div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 9ed737b181..28ad272322 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -1,15 +1,18 @@
- commit = @repository.commit(tag.target)
%li
%h4
- = link_to project_commits_path(@project, tag.name), class: "" do
- %i.icon-tag
+ = link_to namespace_project_commits_path(@project.namespace, @project, tag.name), class: "" do
+ %i.fa.fa-tag
= tag.name
+ - if tag.message.present?
+
+ = strip_gpg_signature(tag.message)
.pull-right
- if can? current_user, :download_code, @project
- = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
+ = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs'
- if can?(current_user, :admin_project, @project)
- = link_to project_tag_path(@project, tag.name), class: 'btn btn-small btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do
- %i.icon-trash
+ = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-xs btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do
+ %i.fa.fa-trash-o
- if commit
%ul.list-unstyled
diff --git a/app/views/projects/tags/destroy.js.haml b/app/views/projects/tags/destroy.js.haml
new file mode 100644
index 0000000000..ada6710f94
--- /dev/null
+++ b/app/views/projects/tags/destroy.js.haml
@@ -0,0 +1,3 @@
+$('.js-totaltags-count').html("#{@repository.tags.size}")
+- if @repository.tags.size == 0
+ $('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index dc3188d43b..f1bc2bc9a2 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -4,26 +4,27 @@
Git Tags
- if can? current_user, :push_code, @project
.pull-right
- = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
- %i.icon-add-sign
+ = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
+ %i.fa.fa-add-sign
New tag
%p.light
Tags give the ability to mark specific points in history as being important
%hr
-- unless @tags.empty?
- %ul.bordered-list
- - @tags.each do |tag|
- = render 'tag', tag: @repository.find_tag(tag)
+.tags
+ - unless @tags.empty?
+ %ul.bordered-list
+ - @tags.each do |tag|
+ = render 'tag', tag: @repository.find_tag(tag)
- = paginate @tags, theme: 'gitlab'
+ = paginate @tags, theme: 'gitlab'
-- else
- .nothing-here-block
- Repository has no tags yet.
- %br
- %small
- Use git tag command to add a new one:
+ - else
+ .nothing-here-block
+ Repository has no tags yet.
%br
- %span.monospace git tag -a v1.4 -m 'version 1.4'
+ %small
+ Use git tag command to add a new one:
+ %br
+ %span.monospace git tag -a v1.4 -m 'version 1.4'
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index a9fd97f891..655044438d 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -1,21 +1,31 @@
+- if @error
+ .alert.alert-danger
+ %button{ type: "button", class: "close", "data-dismiss" => "alert"} ×
+ = @error
%h3.page-title
- %i.icon-code-fork
+ %i.fa.fa-code-fork
New tag
-= form_tag project_tags_path, method: :post, class: "form-horizontal" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal" do
.form-group
= label_tag :tag_name, 'Name for new tag', class: 'control-label'
.col-sm-10
- = text_field_tag :tag_name, nil, placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control'
+ = text_field_tag :tag_name, params[:tag_name], placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
- = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
+ = text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
.light Branch name or commit SHA
+ .form-group
+ = label_tag :message, 'Message', class: 'control-label'
+ .col-sm-10
+ = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
+ .light (Optional) Entering a message will create an annotated tag.
.form-actions
- = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
+ = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
+ = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
+ disableButtonIfAnyEmptyField($("#new-tag-form"), ".form-control", ".btn-create");
var availableTags = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
diff --git a/app/views/projects/team_members/_form.html.haml b/app/views/projects/team_members/_form.html.haml
deleted file mode 100644
index dd059fb99d..0000000000
--- a/app/views/projects/team_members/_form.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-%h3.page-title
- = "New project member(s)"
-
-= form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project), html: { class: "form-horizontal users-project-form" } do |f|
- -if @user_project_relation.errors.any?
- .alert.alert-danger
- %ul
- - @user_project_relation.errors.full_messages.each do |msg|
- %li= msg
-
- %p 1. Choose people you want in the project
- .form-group
- = f.label :user_ids, "People", class: 'control-label'
- .col-sm-10
- = users_select_tag(:user_ids, multiple: true)
-
- %p 2. Set access level for them
- .form-group
- = f.label :project_access, "Project Access", class: 'control-label'
- .col-sm-10= select_tag :project_access, options_for_select(Gitlab::Access.options, @user_project_relation.project_access), class: "project-access-select select2"
-
- .form-actions
- = f.submit 'Add users', class: "btn btn-create"
- = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/team_members/_group_members.html.haml b/app/views/projects/team_members/_group_members.html.haml
deleted file mode 100644
index 83c4b6f87d..0000000000
--- a/app/views/projects/team_members/_group_members.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- group_users_count = @group.users_groups.count
-.panel.panel-default
- .panel-heading
- %strong #{@group.name}
- group members (#{group_users_count})
- .pull-right
- = link_to members_group_path(@group), class: 'btn btn-small' do
- %i.icon-edit
- %ul.well-list
- - @group.users_groups.order('group_access DESC').limit(20).each do |member|
- = render 'users_groups/users_group', member: member, show_controls: false
- - if group_users_count > 20
- %li
- and #{group_users_count - 20} more. For full list visit #{link_to 'group members page', members_group_path(@group)}
diff --git a/app/views/projects/team_members/_team.html.haml b/app/views/projects/team_members/_team.html.haml
deleted file mode 100644
index 0e5b817613..0000000000
--- a/app/views/projects/team_members/_team.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-.team-table
- - can_admin_project = (can? current_user, :admin_project, @project)
- .panel.panel-default
- .panel-heading
- %strong #{@project.name}
- project members (#{members.count})
- %ul.well-list
- - members.each do |team_member|
- = render 'team_member', member: team_member, current_user_can_admin_project: can_admin_project
diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml
deleted file mode 100644
index d93bb44ab9..0000000000
--- a/app/views/projects/team_members/_team_member.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- user = member.user
-%li{id: dom_id(user), class: "team_member_row access-#{member.human_access.downcase}"}
- .pull-right
- - if current_user_can_admin_project
- - unless @project.personal? && user == current_user
- .pull-left
- = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
- = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit"
-
- = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
- %i.icon-minus.icon-white
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
- %p
- %strong= user.name
- %span.cgray= user.username
-
-
diff --git a/app/views/projects/team_members/index.html.haml b/app/views/projects/team_members/index.html.haml
deleted file mode 100644
index ddb3b9d4a9..0000000000
--- a/app/views/projects/team_members/index.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-%h3.page-title
- Users with access to this project
-
- - if can? current_user, :admin_team_member, @project
- %span.pull-right
- = link_to new_project_team_member_path(@project), class: "btn btn-new btn-grouped", title: "New project member" do
- New project member
- = link_to import_project_team_members_path(@project), class: "btn btn-grouped", title: "Import members from another project" do
- Import members
-
-%p.light
- Read more about project permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-= render "team", members: @users_projects
-- if @group
- = render "group_members"
diff --git a/app/views/projects/team_members/new.html.haml b/app/views/projects/team_members/new.html.haml
deleted file mode 100644
index b1bc3ba0eb..0000000000
--- a/app/views/projects/team_members/new.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= render "form"
diff --git a/app/views/projects/team_members/update.js.haml b/app/views/projects/team_members/update.js.haml
deleted file mode 100644
index c68fe9574a..0000000000
--- a/app/views/projects/team_members/update.js.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- if @user_project_relation.valid?
- :plain
- $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#529214"}, 1000);;
-- else
- :plain
- $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#D12F19"}, 1000);;
diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml
index 10b0de98c0..17b9fecfeb 100644
--- a/app/views/projects/transfer.js.haml
+++ b/app/views/projects/transfer.js.haml
@@ -1,7 +1,2 @@
-- if @project.errors[:namespace_id].present?
- :plain
- $("#tab-transfer .errors-holder").replaceWith(errorMessage('#{escape_javascript(@project.errors[:namespace_id].first)}'));
- $("#tab-transfer .form-actions input").removeAttr('disabled').removeClass('disabled');
-- else
- :plain
- location.href = "#{edit_project_path(@project)}";
+:plain
+ location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index 393ef0e24b..02ecbade21 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -1,8 +1,8 @@
%tr{ class: "tree-item #{tree_hex_class(blob_item)}" }
%td.tree-item-file-name
- = tree_icon(type)
+ = tree_icon(type, blob_item.mode, blob_item.name)
%span.str-truncated
- = link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name))
+ = link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name))
%td.tree_time_ago.cgray
= render 'spinner'
%td.hidden-xs.tree_commit
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 9d0292059d..f082d71186 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,6 +1,7 @@
%article.readme-holder#README
- %h4.readme-file-title
- %i.icon-file
- = readme.name
+ = link_to '#README' do
+ %h4.readme-file-title
+ %i.fa.fa-file
+ = readme.name
.wiki
= render_readme(readme)
diff --git a/app/views/projects/tree/_spinner.html.haml b/app/views/projects/tree/_spinner.html.haml
index 5a9e77b63d..b47ad0f41e 100644
--- a/app/views/projects/tree/_spinner.html.haml
+++ b/app/views/projects/tree/_spinner.html.haml
@@ -1,3 +1,3 @@
%span.log_loading.hide
- %i.icon-spinner.icon-spin
+ %i.fa.fa-spinner.fa-spin
Loading commit data...
diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml
index 474604df65..2b5f671c09 100644
--- a/app/views/projects/tree/_submodule_item.html.haml
+++ b/app/views/projects/tree/_submodule_item.html.haml
@@ -1,14 +1,6 @@
-- tree, commit = submodule_links(submodule_item)
%tr{ class: "tree-item" }
%td.tree-item-file-name
- %i.icon-archive
- %span
- = link_to truncate(submodule_item.name, length: 40), tree
- @
- %span.monospace
- - if commit.nil?
- #{submodule_item.id[0..10]}
- - else
- = link_to "#{submodule_item.id[0..10]}", commit
+ %i.fa.fa-archive.fa-fw
+ = submodule_link(submodule_item, @ref)
%td
%td.hidden-xs
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml
index 84c3682d7a..d304690d16 100644
--- a/app/views/projects/tree/_tree.html.haml
+++ b/app/views/projects/tree/_tree.html.haml
@@ -1,18 +1,18 @@
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to project_tree_path(@project, @ref) do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
- tree_breadcrumbs(tree, 6) do |title, path|
%li
- if path
- = link_to truncate(title, length: 40), project_tree_path(@project, path)
+ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to title, '#'
- if current_user && can_push_branch?(@project, @ref)
%li
- = link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'New file', id: 'new-file-link' do
%small
- %i.icon-plus
+ %i.fa.fa-plus
%div#tree-content-holder.tree-content-holder
%table#tree-slider{class: "table_#{@hex_path} tree-table" }
@@ -24,18 +24,18 @@
.pull-left Last Commit
.last-commit.hidden-sm.pull-left
- %i.icon-angle-right
+ %i.fa.fa-angle-right
%small.light
- = link_to @commit.short_id, project_commit_path(@project, @commit)
+ = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit)
–
= truncate(@commit.title, length: 50)
- = link_to "history", project_commits_path(@project, @id), class: "pull-right"
+ = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
- if @path.present?
%tr.tree-item
%td.tree-item-file-name
- = link_to "..", project_tree_path(@project, up_dir_path(tree)), class: 'prepend-left-10'
+ = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10'
%td
%td.hidden-xs
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index bd50dd4d9a..50521264a6 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,3 +1,3 @@
%span.str-truncated
%span.tree_author= commit_author_link(commit, avatar: true, size: 16)
- = link_to_gfm commit.title, project_commit_path(@project, commit.id), class: "tree-commit-link"
+ = link_to_gfm commit.title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index f8cecf9be1..e87138bf98 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -1,8 +1,9 @@
%tr{ class: "tree-item #{tree_hex_class(tree_item)}" }
%td.tree-item-file-name
- = tree_icon(type)
+ = tree_icon(type, tree_item.mode, tree_item.name)
%span.str-truncated
- = link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name))
+ - path = flatten_tree(tree_item)
+ = link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path))
%td.tree_time_ago.cgray
= render 'spinner'
%td.hidden-xs.tree_commit
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index fc4616da6e..feca145369 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -3,7 +3,7 @@
- if can? current_user, :download_code, @project
.tree-download-holder
- = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-small pull-right hidden-xs hidden-sm', split_button: true
+ = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-sm pull-right hidden-xs hidden-sm', split_button: true
#tree-holder.tree-holder.clearfix
= render "tree", tree: @tree
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index cbb21f2b9f..4f3f4cab8d 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -1,6 +1,6 @@
- if @project.valid?
:plain
- location.href = "#{edit_project_path(@project)}";
+ location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 0a24e36ae8..9fbfa0b1ae 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f|
-if @page.errors.any?
#error_explanation
.alert.alert-danger
@@ -19,13 +19,15 @@
%code [Link Title](page-slug)
\.
- .form-group
+ .form-group.wiki-content
= f.label :content, class: 'control-label'
.col-sm-10
- = f.text_area :content, class: 'form-control js-gfm-input markdown-area', rows: 18
- .col-sm-12.hint
- .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
+ .col-sm-12.hint
+ .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
+ .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+
.clearfix
.error-alert
.form-group
@@ -35,11 +37,10 @@
.form-actions
- if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn"
- = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel"
- else
= f.submit 'Create page', class: "btn-create btn"
- = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
:javascript
- window.project_image_path_upload = "#{upload_image_project_path @project}";
-
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 68d7087323..633214a4e8 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,8 +1,8 @@
%span.pull-right
- if (@page && @page.persisted?)
- = link_to history_project_wiki_path(@project, @page), class: "btn btn-grouped" do
+ = link_to history_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
Page History
- if can?(current_user, :write_wiki, @project)
- = link_to edit_project_wiki_path(@project, @page), class: "btn btn-grouped" do
- %i.icon-edit
+ = link_to edit_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
+ %i.fa.fa-pencil-square-o
Edit
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 0a7e51e974..693c3facb3 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,19 +1,19 @@
%ul.nav.nav-tabs
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
- = link_to 'Home', project_wiki_path(@project, :home)
+ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
= nav_link(path: 'wikis#pages') do
- = link_to 'Pages', pages_project_wikis_path(@project)
+ = link_to 'Pages', pages_namespace_project_wikis_path(@project.namespace, @project)
= nav_link(path: 'wikis#git_access') do
- = link_to git_access_project_wikis_path(@project) do
- %i.icon-download-alt
+ = link_to git_access_namespace_project_wikis_path(@project.namespace, @project) do
+ %i.fa.fa-download
Git Access
- if can?(current_user, :write_wiki, @project)
.pull-right
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- %i.icon-plus
+ %i.fa.fa-plus
New Page
= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 1ce292a02d..6834969de8 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -7,7 +7,7 @@
.modal-body
= label_tag :new_wiki_path do
%span Page slug
- = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project)
+ = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project)
%p.hint
Please don't use spaces.
.modal-footer
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 5347caf000..566850cb78 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -9,5 +9,5 @@
.pull-right
- if @page.persisted? && can?(current_user, :admin_wiki, @project)
- = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-small btn-remove" do
+ = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-sm btn-remove" do
Delete this page
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 7bc566cf7f..91291f753f 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,7 +1,7 @@
= render 'nav'
%h3.page-title
%span.light History for
- = link_to @page.title, project_wiki_path(@project, @page)
+ = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
%table.table
%thead
@@ -12,18 +12,19 @@
%th Last updated
%th Format
%tbody
- - @page.versions.each do |version|
+ - @page.versions.each_with_index do |version, index|
- commit = version
%tr
%td
- = link_to project_wiki_path(@project, @page, version_id: commit.id) do
- = commit.short_id
+ = link_to project_wiki_path_with_version(@project, @page,
+ commit.id, index == 0) do
+ = truncate_sha(commit.id)
%td
- = commit_author_link(commit, avatar: true, size: 24)
+ = commit.author.name
%td
- = commit.title
+ = commit.message
%td
- #{time_ago_with_tooltip(version.date)}
+ #{time_ago_with_tooltip(version.authored_date)}
%td
%strong
= @page.page.wiki.page(@page.page.name, commit.id).try(:format)
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 74317faf9d..ee233d9086 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -5,8 +5,8 @@
- @wiki_pages.each do |wiki_page|
%li
%h4
- = link_to wiki_page.title, project_wiki_path(@project, wiki_page)
+ = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
%small (#{wiki_page.format})
.pull-right
- %small Last edited #{time_ago_with_tooltip(wiki_page.commit.created_at)}
+ %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
= paginate @wiki_pages, theme: 'gitlab'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index cb923e4ca3..a6263e93f6 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -5,7 +5,7 @@
- if @page.historical?
.warning_message
This is an old version of this page.
- You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", history_project_wiki_path(@project, @page)}.
+ You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", history_namespace_project_wiki_path(@project.namespace, @project, @page)}.
%hr
@@ -17,4 +17,4 @@
%hr
.wiki-last-edit-by
- Last edited by #{commit_author_link(@page.commit, avatar: true, size: 16)} #{time_ago_with_tooltip(@page.commit.created_at)}
+ Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 979b18e385..ffc145497a 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -1,6 +1,6 @@
.dropdown.inline
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-tags
+ %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
+ %i.fa.fa-tags
%span.light Group:
- if @group.present?
%strong= @group.name
@@ -9,16 +9,16 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to search_path(group_id: nil, search: params[:search]) do
+ = link_to search_filter_path(group_id: nil) do
Any
- current_user.authorized_groups.sort_by(&:name).each do |group|
%li
- = link_to search_path(group_id: group.id, search: params[:search]) do
+ = link_to search_filter_path(group_id: group.id, project_id: nil) do
= group.name
-.dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-tags
+.dropdown.inline.prepend-left-10.project-filter
+ %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
+ %i.fa.fa-tags
%span.light Project:
- if @project.present?
%strong= @project.name_with_namespace
@@ -27,9 +27,9 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to search_path(project_id: nil, search: params[:search]) do
+ = link_to search_filter_path(project_id: nil) do
Any
- current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project|
%li
- = link_to search_path(project_id: project.id, search: params[:search]) do
+ = link_to search_filter_path(project_id: project.id, group_id: nil) do
= project.name_with_namespace
diff --git a/app/views/search/_global_filter.html.haml b/app/views/search/_global_filter.html.haml
new file mode 100644
index 0000000000..442bd84f93
--- /dev/null
+++ b/app/views/search/_global_filter.html.haml
@@ -0,0 +1,16 @@
+%ul.nav.nav-pills.nav-stacked.search-filter
+ %li{class: ("active" if @scope == 'projects')}
+ = link_to search_filter_path(scope: 'projects') do
+ Projects
+ .pull-right
+ = @search_results.projects_count
+ %li{class: ("active" if @scope == 'issues')}
+ = link_to search_filter_path(scope: 'issues') do
+ Issues
+ .pull-right
+ = @search_results.issues_count
+ %li{class: ("active" if @scope == 'merge_requests')}
+ = link_to search_filter_path(scope: 'merge_requests') do
+ Merge requests
+ .pull-right
+ = @search_results.merge_requests_count
diff --git a/app/views/search/_global_results.html.haml b/app/views/search/_global_results.html.haml
deleted file mode 100644
index 7f4f0e5e00..0000000000
--- a/app/views/search/_global_results.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-.search_results
- %ul.bordered-list
- = render partial: "search/results/project", collection: @search_results[:projects]
- = render partial: "search/results/merge_request", collection: @search_results[:merge_requests]
- = render partial: "search/results/issue", collection: @search_results[:issues]
diff --git a/app/views/search/_project_filter.html.haml b/app/views/search/_project_filter.html.haml
new file mode 100644
index 0000000000..ad933502a2
--- /dev/null
+++ b/app/views/search/_project_filter.html.haml
@@ -0,0 +1,32 @@
+%ul.nav.nav-pills.nav-stacked.search-filter
+ %li{class: ("active" if @scope == 'blobs')}
+ = link_to search_filter_path(scope: 'blobs') do
+ %i.fa.fa-code
+ Code
+ .pull-right
+ = @search_results.blobs_count
+ %li{class: ("active" if @scope == 'issues')}
+ = link_to search_filter_path(scope: 'issues') do
+ %i.fa.fa-exclamation-circle
+ Issues
+ .pull-right
+ = @search_results.issues_count
+ %li{class: ("active" if @scope == 'merge_requests')}
+ = link_to search_filter_path(scope: 'merge_requests') do
+ %i.fa.fa-code-fork
+ Merge requests
+ .pull-right
+ = @search_results.merge_requests_count
+ %li{class: ("active" if @scope == 'notes')}
+ = link_to search_filter_path(scope: 'notes') do
+ %i.fa.fa-comments
+ Comments
+ .pull-right
+ = @search_results.notes_count
+ %li{class: ("active" if @scope == 'wiki_blobs')}
+ = link_to search_filter_path(scope: 'wiki_blobs') do
+ %i.fa.fa-book
+ Wiki
+ .pull-right
+ = @search_results.wiki_blobs_count
+
diff --git a/app/views/search/_project_results.html.haml b/app/views/search/_project_results.html.haml
deleted file mode 100644
index 5e8346a826..0000000000
--- a/app/views/search/_project_results.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-%ul.nav.nav-tabs
- %li{class: ("active" if params[:search_code].present?)}
- = link_to search_path(params.merge(search_code: true)) do
- Repository Code
- %li{class: ("active" if params[:search_code].blank?)}
- = link_to search_path(params.merge(search_code: nil)) do
- Issues and Merge requests
-
-.search_results
- - if params[:search_code].present?
- .blob-results
- - if !@search_results[:blobs].empty?
- = render partial: "search/results/blob", collection: @search_results[:blobs]
- = paginate @search_results[:blobs], theme: 'gitlab'
- - else
- = render partial: "search/results/empty", :locals => { message: "We couldn't find any matching code" }
- - else
- - if @search_results[:merge_requests].present? || @search_results[:issues].present? || @search_results[:notes].present?
- %ul.bordered-list
- = render partial: "search/results/merge_request", collection: @search_results[:merge_requests]
- = render partial: "search/results/issue", collection: @search_results[:issues]
- = render partial: "search/results/note", collection: @search_results[:notes]
- - else
- = render partial: "search/results/empty", locals: { message: "We couldn't find any issues, merge requests or notes" }
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 2336d0f71d..796dd752a4 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,17 +1,28 @@
%h4
- #{@search_results[:total_results]} results found
- - if @project
- for #{link_to @project.name_with_namespace, @project}
- - elsif @group
- for #{link_to @group.name, @group}
+ #{@search_results.total_count} results found
+ - unless @show_snippets
+ - if @project
+ for #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]}
+ - elsif @group
+ for #{link_to @group.name, @group}
%hr
-- if @project
- = render "project_results"
-- else
- = render "global_results"
+.row
+ .col-sm-3
+ - if @project
+ = render "project_filter"
+ - elsif @show_snippets
+ = render 'snippet_filter'
+ - else
+ = render "global_filter"
+ .col-sm-9
+ .search-results
+ - if @search_results.empty?
+ = render partial: "search/results/empty", locals: { message: "We couldn't find any matching results" }
+ - else
+ = render partial: "search/results/#{@scope.singularize}", collection: @objects
+ = paginate @objects, theme: 'gitlab'
:javascript
- $(".search_results .term").highlight("#{escape_javascript(params[:search])}");
-
+ $(".search-results .term").highlight("#{escape_javascript(params[:search])}");
diff --git a/app/views/search/_snippet_filter.html.haml b/app/views/search/_snippet_filter.html.haml
new file mode 100644
index 0000000000..95d23fa9f4
--- /dev/null
+++ b/app/views/search/_snippet_filter.html.haml
@@ -0,0 +1,13 @@
+%ul.nav.nav-pills.nav-stacked.search-filter
+ %li{class: ("active" if @scope == 'snippet_blobs')}
+ = link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
+ %i.fa.fa-code
+ Snippet Contents
+ .pull-right
+ = @search_results.snippet_blobs_count
+ %li{class: ("active" if @scope == 'snippet_titles')}
+ = link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
+ %i.fa.fa-book
+ Titles and Filenames
+ .pull-right
+ = @search_results.snippet_titles_count
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index f9d217e840..84e9be82c4 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,9 +1,9 @@
.blob-result
.file-holder
.file-title
- = link_to project_blob_path(@project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
- %i.icon-file
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
+ %i.fa.fa-file
%strong
= blob.filename
.file-content.code.term
- = render 'shared/file_hljs', blob: blob, first_line_number: blob.startline
+ = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, user_color_scheme_class: 'white'
diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml
index 3615f6ae52..01fb8cd9b8 100644
--- a/app/views/search/results/_empty.html.haml
+++ b/app/views/search/results/_empty.html.haml
@@ -1,4 +1,4 @@
.search_box
.search_glyph
- %span.icon-search
+ %span.fa.fa-search
%h4 #{message}
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 8147cf272f..ce8ddff955 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -1,9 +1,14 @@
-%li
- issue:
- = link_to [issue.project, issue] do
- %span ##{issue.iid}
- %strong.term
- = truncate issue.title, length: 50
- %span.light (#{issue.project.name_with_namespace})
+.search-result-row
+ %h4
+ = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
+ %span.term.str-truncated= issue.title
+ .pull-right ##{issue.iid}
+ - if issue.description.present?
+ .description.term
+ = preserve do
+ = search_md_sanitize(markdown(issue.description))
+ %span.light
+ #{issue.project.name_with_namespace}
- if issue.closed?
- %span.label.label-danger Closed
+ .pull-right
+ %span.label.label-danger Closed
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index de2a79970c..2efa616d66 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -1,14 +1,16 @@
-%li
- merge request:
- = link_to [merge_request.target_project, merge_request] do
- %span ##{merge_request.iid}
- %strong.term
- = truncate merge_request.title, length: 50
- - if merge_request.for_fork?
- %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} → #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch})
- - else
- %span.light (#{merge_request.source_branch} → #{merge_request.target_branch})
- - if merge_request.merged?
- %span.label.label-primary Merged
- - elsif merge_request.closed?
- %span.label.label-danger Closed
+.search-result-row
+ %h4
+ = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
+ %span.term.str-truncated= merge_request.title
+ .pull-right ##{merge_request.iid}
+ - if merge_request.description.present?
+ .description.term
+ = preserve do
+ = search_md_sanitize(markdown(merge_request.description))
+ %span.light
+ #{merge_request.project.name_with_namespace}
+ .pull-right
+ - if merge_request.merged?
+ %span.label.label-primary Merged
+ - elsif merge_request.closed?
+ %span.label.label-danger Closed
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 97e892bdd4..5fcba2b7e9 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,9 +1,26 @@
-%li
- note on issue:
- = link_to [note.project, note.noteable] do
- %span ##{note.noteable.iid}
- %strong.term
- = truncate note.noteable.title, length: 50
- %span.light (#{note.project.name_with_namespace})
- - if note.noteable.closed?
- %span.label Closed
+- project = note.project
+.search-result-row
+ %h5.note-search-caption.str-truncated
+ %i.fa.fa-comment
+ = link_to_member(project, note.author, avatar: false)
+ commented on
+
+ - if note.for_commit?
+ = link_to project do
+ = project.name_with_namespace
+ ·
+ = link_to namespace_project_commit_path(project.namespace, project, note.commit_id, anchor: dom_id(note)) do
+ Commit #{truncate_sha(note.commit_id)}
+ - else
+ = link_to project do
+ = project.name_with_namespace
+ ·
+ %span #{note.noteable_type.titleize} ##{note.noteable.iid}
+ ·
+ = link_to [project.namespace.becomes(Namespace), project, note.noteable, anchor: dom_id(note)] do
+ = note.noteable.title
+
+ .note-search-result
+ .term
+ = preserve do
+ = search_md_sanitize(markdown(note.note, {no_header_anchors: true}))
diff --git a/app/views/search/results/_project.html.haml b/app/views/search/results/_project.html.haml
index abc86c72be..195cf06c8e 100644
--- a/app/views/search/results/_project.html.haml
+++ b/app/views/search/results/_project.html.haml
@@ -1,7 +1,6 @@
-%li
- project:
- = link_to project do
- %strong.term= project.name_with_namespace
+.search-result-row
+ %h4
+ = link_to [project.namespace.becomes(Namespace), project] do
+ %span.term= project.name_with_namespace
- if project.description.present?
- –
%span.light.term= project.description
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
new file mode 100644
index 0000000000..8af393777f
--- /dev/null
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -0,0 +1,59 @@
+.search-result-row
+ %span
+ = snippet_blob[:snippet_object].title
+ by
+ = link_to user_snippets_path(snippet_blob[:snippet_object].author) do
+ = image_tag avatar_icon(snippet_blob[:snippet_object].author_email), class: "avatar avatar-inline s16", alt: ''
+ = snippet_blob[:snippet_object].author_name
+ %span.light #{time_ago_with_tooltip(snippet_blob[:snippet_object].created_at)}
+ %h4.snippet-title
+ - snippet_path = reliable_snippet_path(snippet_blob[:snippet_object])
+ = link_to snippet_path do
+ .file-holder
+ .file-title
+ %i.fa.fa-file
+ %strong= snippet_blob[:snippet_object].file_name
+ - if gitlab_markdown?(snippet_blob[:snippet_object].file_name)
+ .file-content.wiki
+ - snippet_blob[:snippet_chunks].each do |snippet|
+ - unless snippet[:data].empty?
+ = preserve do
+ = markdown(snippet[:data])
+ - else
+ .file-content.code
+ .nothing-here-block Empty file
+ - elsif markup?(snippet_blob[:snippet_object].file_name)
+ .file-content.wiki
+ - snippet_blob[:snippet_chunks].each do |snippet|
+ - unless snippet[:data].empty?
+ = render_markup(snippet_blob[:snippet_object].file_name, snippet[:data])
+ - else
+ .file-content.code
+ .nothing-here-block Empty file
+ - else
+ .file-content.code
+ %div.highlighted-data{class: user_color_scheme_class}
+ .line-numbers
+ - snippet_blob[:snippet_chunks].each do |snippet|
+ - unless snippet[:data].empty?
+ - snippet[:data].lines.to_a.size.times do |index|
+ - offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1
+ - i = index + offset
+ = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}" do
+ %i.fa.fa-link
+ = i
+ - unless snippet == snippet_blob[:snippet_chunks].last
+ %a
+ = "."
+ .highlight.term
+ %pre
+ %code
+ - snippet_blob[:snippet_chunks].each do |snippet|
+ - unless snippet[:data].empty?
+ = snippet[:data]
+ - unless snippet == snippet_blob[:snippet_chunks].last
+ %a
+ = "..."
+ - else
+ .file-content.code
+ .nothing-here-block Empty file
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
new file mode 100644
index 0000000000..c414acb6a1
--- /dev/null
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -0,0 +1,23 @@
+.search-result-row
+ %h4.snippet-title.term
+ = link_to reliable_snippet_path(snippet_title) do
+ = truncate(snippet_title.title, length: 60)
+ - if snippet_title.private?
+ %span.label.label-gray
+ %i.fa.fa-lock
+ private
+ %span.cgray.monospace.tiny.pull-right.term
+ = snippet_title.file_name
+
+ %small.pull-right.cgray
+ - if snippet_title.project_id?
+ = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project)
+
+ .snippet-info
+ = "##{snippet_title.id}"
+ %span
+ by
+ = link_to user_snippets_path(snippet_title.author) do
+ = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: ''
+ = snippet_title.author_name
+ %span.light #{time_ago_with_tooltip(snippet_title.created_at)}
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
new file mode 100644
index 0000000000..f9c5810e3d
--- /dev/null
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -0,0 +1,9 @@
+.blob-result
+ .file-holder
+ .file-title
+ = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.filename) do
+ %i.fa.fa-file
+ %strong
+ = wiki_blob.filename
+ .file-content.code.term
+ = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline, user_color_scheme_class: 'white'
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 3b6f10d4d9..5b4816e4c4 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -6,14 +6,16 @@
.col-sm-6
= search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search"
.col-sm-4
- = submit_tag 'Search', class: "btn btn-create"
+ = button_tag 'Search', class: "btn btn-create"
.form-group
.col-sm-2
- .col-sm-10
- = render 'filter', f: f
+ - unless params[:snippets].eql? 'true'
+ .col-sm-10
+ = render 'filter', f: f
= hidden_field_tag :project_id, params[:project_id]
= hidden_field_tag :group_id, params[:group_id]
- = hidden_field_tag :search_code, params[:search_code]
+ = hidden_field_tag :snippets, params[:snippets]
+ = hidden_field_tag :scope, params[:scope]
.results.prepend-top-10
- if params[:search].present?
diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml
new file mode 100644
index 0000000000..000532b1c9
--- /dev/null
+++ b/app/views/shared/_choose_group_avatar_button.html.haml
@@ -0,0 +1,7 @@
+%a.choose-btn.btn.btn-sm.js-choose-group-avatar-button
+ %i.fa.fa-paperclip
+ %span Choose File ...
+
+%span.file_name.js-avatar-filename File name...
+= f.file_field :avatar, class: 'js-group-avatar-input hidden'
+.light The maximum file size allowed is 200KB.
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 8cd426c71e..a1121750ca 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -1,6 +1,23 @@
- project = project || @project
.git-clone-holder.input-group
.input-group-btn
- %button{class: "btn #{ 'active' if default_clone_protocol == 'ssh' }", :"data-clone" => project.ssh_url_to_repo} SSH
- %button{class: "btn #{ 'active' if default_clone_protocol == 'http' }", :"data-clone" => project.http_url_to_repo}= gitlab_config.protocol.upcase
+ %button{ |
+ class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
+ :"data-clone" => project.ssh_url_to_repo, |
+ :"data-title" => "Add an SSH key to your profile to pull or push via SSH",
+ :"data-html" => "true",
+ :"data-container" => "body"}
+ SSH
+ %button{ |
+ class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
+ :"data-clone" => project.http_url_to_repo, |
+ :"data-title" => "Set a password on your account to pull or push via #{gitlab_config.protocol.upcase}",
+ :"data-html" => "true",
+ :"data-container" => "body"}
+ = gitlab_config.protocol.upcase
= text_field_tag :project_clone, default_url_to_repo(project), class: "one_click_select form-control", readonly: true
+ - if project.kind_of?(Project)
+ .input-group-addon
+ .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" }
+ = visibility_level_icon(project.visibility_level)
+ = visibility_level_label(project.visibility_level).downcase
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index 4365947e70..5071ff640f 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -1,3 +1,14 @@
-.commit-message-container
- .max-width-marker
- = textarea
+.form-group.commit_message-group
+ = label_tag 'commit_message', class: 'control-label' do
+ Commit message
+ .col-sm-10
+ .commit-message-container
+ .max-width-marker
+ = text_area_tag 'commit_message',
+ (params[:commit_message] || local_assigns[:text]),
+ class: 'form-control', placeholder: local_assigns[:placeholder],
+ required: true, rows: (local_assigns[:rows] || 3)
+ - if local_assigns[:hint]
+ %p.hint
+ Try to keep the first line under 52 characters
+ and the others under 72.
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
new file mode 100644
index 0000000000..30ba361c86
--- /dev/null
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -0,0 +1,22 @@
+#modal-confirm-danger.modal.hide{tabindex: -1}
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h4 Confirmation required
+
+ .modal-body
+ %p.cred.lead.js-confirm-text
+
+ %p
+ This action can lead to data loss.
+ To prevent accidental actions we ask you to confirm your intention.
+ %br
+ Please type
+ %code.js-confirm-danger-match #{phrase}
+ to proceed or close this modal to cancel
+
+ .form-group
+ = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
+ .form-group
+ = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index ee0b57fbe5..d07a9e2b92 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,5 +1,19 @@
-.event_filter
+%ul.nav.nav-pills.event_filter
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
+
+ - if current_user
+ - if current_controller?(:dashboard)
+ %li.pull-right
+ = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
+ %i.fa.fa-rss
+ News Feed
+
+ - if current_controller?(:groups)
+ %li.pull-right
+ = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do
+ %i.fa.fa-rss
+ News Feed
+%hr
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
new file mode 100644
index 0000000000..fba69dd0f3
--- /dev/null
+++ b/app/views/shared/_file_highlight.html.haml
@@ -0,0 +1,11 @@
+.file-content.code{class: user_color_scheme_class}
+ .line-numbers
+ - if blob.data.present?
+ - blob.data.lines.to_a.size.times do |index|
+ - offset = defined?(first_line_number) ? first_line_number : 1
+ - i = index + offset
+ = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do
+ %i.fa.fa-link
+ = i
+ :preserve
+ #{highlight(blob.name, blob.data)}
diff --git a/app/views/shared/_file_hljs.html.haml b/app/views/shared/_file_hljs.html.haml
deleted file mode 100644
index ceee2c7527..0000000000
--- a/app/views/shared/_file_hljs.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%div.highlighted-data{class: user_color_scheme_class}
- .line-numbers
- - blob.data.lines.to_a.size.times do |index|
- - offset = defined?(first_line_number) ? first_line_number : 1
- - i = index + offset
- = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do
- %i.icon-link
- = i
- .highlight
- %pre
- %code{ class: highlightjs_class(blob.name) }
- = blob.data
diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml
deleted file mode 100644
index 9e65ce11ad..0000000000
--- a/app/views/shared/_filter.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-.side-filters
- = form_tag filter_path(entity), method: 'get' do
- - if current_user
- %fieldset.scope-filter
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:scope] == 'assigned-to-me')}
- = link_to filter_path(entity, scope: 'assigned-to-me') do
- Assigned to me
- %span.pull-right
- = assigned_entities_count(current_user, entity, @group)
- %li{class: ("active" if params[:scope] == 'authored')}
- = link_to filter_path(entity, scope: 'authored') do
- Created by me
- %span.pull-right
- = authored_entities_count(current_user, entity, @group)
- %li{class: ("active" if params[:scope] == 'all')}
- = link_to filter_path(entity, scope: 'all') do
- Everyone's
- %span.pull-right
- = authorized_entities_count(current_user, entity, @group)
-
- %fieldset.status-filter
- %legend State
- %ul.nav.nav-pills
- %li{class: ("active" if params[:state] == 'opened')}
- = link_to filter_path(entity, state: 'opened') do
- Open
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to filter_path(entity, state: 'closed') do
- Closed
- %li{class: ("active" if params[:state] == 'all')}
- = link_to filter_path(entity, state: 'all') do
- All
-
- %fieldset
- %legend Projects
- %ul.nav.nav-pills.nav-stacked.nav-small
- - @projects.each do |project|
- - unless entities_per_project(project, entity).zero?
- %li{class: ("active" if params[:project_id] == project.id.to_s)}
- = link_to filter_path(entity, project_id: project.id) do
- = project.name_with_namespace
- %small.pull-right= entities_per_project(project, entity)
-
- %fieldset
- - if params[:state].present? || params[:project_id].present?
- = link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do
- %i.icon-remove
- %strong Clear filter
-
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
new file mode 100644
index 0000000000..c0a9923348
--- /dev/null
+++ b/app/views/shared/_group_form.html.haml
@@ -0,0 +1,29 @@
+- if @group.persisted?
+ .form-group
+ = f.label :name, class: 'control-label' do
+ Group name
+ .col-sm-10
+ = f.text_field :name, placeholder: 'open-source', class: 'form-control'
+
+.form-group
+ = f.label :path, class: 'control-label' do
+ Group path
+ .col-sm-10
+ .input-group
+ .input-group-addon
+ = root_url
+ = f.text_field :path, placeholder: 'open-source', class: 'form-control',
+ autofocus: local_assigns[:autofocus] || false
+ - if @group.persisted?
+ .alert.alert-warning.prepend-top-10
+ %ul
+ %li Changing group path can have unintended side effects.
+ %li Renaming group path will rename directory for all related projects
+ %li It will change web url for access group and group projects.
+ %li It will change the git path to repositories under this group.
+
+.form-group.group-description-holder
+ = f.label :description, 'Details', class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, maxlength: 250,
+ class: 'form-control js-gfm-input', rows: 4
diff --git a/app/views/shared/_group_tips.html.haml b/app/views/shared/_group_tips.html.haml
new file mode 100644
index 0000000000..e5cf783beb
--- /dev/null
+++ b/app/views/shared/_group_tips.html.haml
@@ -0,0 +1,6 @@
+%ul
+ %li A group is a collection of several projects
+ %li Groups are private by default
+ %li Members of a group may only view projects they have permission to access
+ %li Group project URLs are prefixed with the group namespace
+ %li Existing projects may be moved into a group
diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml
new file mode 100644
index 0000000000..83f5a3a801
--- /dev/null
+++ b/app/views/shared/_issuable_filter.html.haml
@@ -0,0 +1,58 @@
+.issues-filters
+ .issues-state-filters
+ %ul.nav.nav-tabs
+ %li{class: ("active" if params[:state] == 'opened')}
+ = link_to page_filter_path(state: 'opened') do
+ %i.fa.fa-exclamation-circle
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed') do
+ %i.fa.fa-check-circle
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to page_filter_path(state: 'all') do
+ %i.fa.fa-compass
+ All
+
+ .issues-details-filters
+ = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_id, :label_name]), method: :get, class: 'filter-form' do
+ - if controller.controller_name == 'issues'
+ .check-all-holder
+ = check_box_tag "check_all_issues", nil, false,
+ class: "check_all_issues left",
+ disabled: !can?(current_user, :modify_issue, @project)
+ .issues-other-filters
+ .filter-item.inline
+ = users_select_tag(:assignee_id, selected: params[:assignee_id],
+ placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true)
+
+ .filter-item.inline
+ = users_select_tag(:author_id, selected: params[:author_id],
+ placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true)
+
+ .filter-item.inline.milestone-filter
+ = select_tag('milestone_id', projects_milestones_options, class: "select2 trigger-submit", prompt: 'Milestone')
+
+ - if @project
+ .filter-item.inline.labels-filter
+ = select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit", prompt: 'Label')
+
+ .pull-right
+ = render 'shared/sort_dropdown'
+
+ - if controller.controller_name == 'issues'
+ .issues_bulk_update.hide
+ = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
+ = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control')
+ = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true)
+ = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
+ = hidden_field_tag 'update[issues_ids]', []
+ = hidden_field_tag :state_event, params[:state_event]
+ = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+
+:coffeescript
+ new UsersSelect()
+
+ $('form.filter-form').on 'submit', (event) ->
+ event.preventDefault()
+ Turbolinks.visit @.action + '&' + $(@).serialize()
diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/_issuable_search_form.html.haml
new file mode 100644
index 0000000000..639d203dcd
--- /dev/null
+++ b/app/views/shared/_issuable_search_form.html.haml
@@ -0,0 +1,9 @@
+= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
+ .append-right-10.hidden-xs.hidden-sm
+ = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
+ = hidden_field_tag :state, params['state']
+ = hidden_field_tag :scope, params['scope']
+ = hidden_field_tag :assignee_id, params['assignee_id']
+ = hidden_field_tag :author_id, params['author_id']
+ = hidden_field_tag :milestone_id, params['milestone_id']
+ = hidden_field_tag :label_id, params['label_id']
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index e976f897dc..0dbb6a0439 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -4,7 +4,7 @@
- project = group[0]
.panel-heading
= link_to_project project
- = link_to 'show all', project_issues_path(project), class: 'pull-right'
+ = link_to 'show all', namespace_project_issues_path(project.namespace, project), class: 'pull-right'
%ul.well-list.issues-list
- group[1].each do |issue|
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index 39a1ee38f8..c02c5af008 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -4,7 +4,7 @@
- project = group[0]
.panel-heading
= link_to_project project
- = link_to 'show all', project_merge_requests_path(project), class: 'pull-right'
+ = link_to 'show all', namespace_project_merge_requests_path(project.namespace, project), class: 'pull-right'
%ul.well-list.mr-list
- group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
new file mode 100644
index 0000000000..f685ae7726
--- /dev/null
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -0,0 +1,14 @@
+.milestones-filters.append-bottom-10
+ %ul.nav.nav-tabs
+ %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
+ = link_to milestones_filter_path(state: 'opened') do
+ %i.fa.fa-exclamation-circle
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to milestones_filter_path(state: 'closed') do
+ %i.fa.fa-check-circle
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to milestones_filter_path(state: 'all') do
+ %i.fa.fa-compass
+ All
diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml
new file mode 100644
index 0000000000..a43bf33751
--- /dev/null
+++ b/app/views/shared/_no_password.html.haml
@@ -0,0 +1,8 @@
+- if cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && current_user.require_password?
+ .no-password-message.alert.alert-warning.hidden-xs
+ You won't be able to pull or push project code via #{gitlab_config.protocol.upcase} until you #{link_to 'set a password', edit_profile_password_path} on your account
+
+ .pull-right
+ = link_to "Don't show again", profile_path(user: {hide_no_password: true}), method: :put
+ |
+ = link_to 'Remind later', '#', class: 'hide-no-password-message'
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index e70eb4d01b..089179e677 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,14 +1,8 @@
-- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key
- .no-ssh-key-message
- .container
- You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile
- .pull-right.hidden-xs
- = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
- |
- = link_to 'Remind later', '#', class: 'hide-no-ssh-message'
- .links-xs.visible-xs
- = link_to "Add key", new_profile_key_path
- |
- = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
- |
- = link_to 'Later', '#', class: 'hide-no-ssh-message'
+- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+ .no-ssh-key-message.alert.alert-warning.hidden-xs
+ You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path, class: 'alert-link'} to your profile
+
+ .pull-right
+ = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link'
+ |
+ = link_to 'Remind later', '#', class: 'hide-no-ssh-message alert-link'
diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml
new file mode 100644
index 0000000000..0eba1fe075
--- /dev/null
+++ b/app/views/shared/_outdated_browser.html.haml
@@ -0,0 +1,8 @@
+- if outdated_browser?
+ - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers"
+ .browser-alert
+ GitLab may not work properly because you are using an outdated web browser.
+ %br
+ Please install a
+ = link_to 'supported web browser', link
+ for a better experience.
diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml
new file mode 100644
index 0000000000..722a7f7ce0
--- /dev/null
+++ b/app/views/shared/_project.html.haml
@@ -0,0 +1,21 @@
+= cache [project.namespace, project, controller.controller_name, controller.action_name] do
+ = link_to project_path(project), class: dom_class(project) do
+ - if avatar
+ .dash-project-avatar
+ = project_icon(project, alt: '', class: 'avatar project-avatar s40')
+ .dash-project-access-icon
+ = visibility_level_icon(project.visibility_level)
+ %span.str-truncated
+ %span.namespace-name
+ - if project.namespace
+ = project.namespace.human_name
+ \/
+ %span.project-name.filter-title
+ = project.name
+ - if stars
+ %span.pull-right.light
+ %i.fa.fa-star
+ = project.star_count
+ - else
+ %span.arrow
+ %i.fa.fa-angle-right
diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml
deleted file mode 100644
index 5e7d1c2c88..0000000000
--- a/app/views/shared/_project_filter.html.haml
+++ /dev/null
@@ -1,64 +0,0 @@
-.side-filters
- = form_tag project_entities_path, method: 'get' do
- - if current_user
- %fieldset
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:scope] == 'all')}
- = link_to project_filter_path(scope: 'all') do
- Everyone's
- %span.pull-right
- = authorized_entities_count(current_user, entity, @project)
- %li{class: ("active" if params[:scope] == 'assigned-to-me')}
- = link_to project_filter_path(scope: 'assigned-to-me') do
- Assigned to me
- %span.pull-right
- = assigned_entities_count(current_user, entity, @project)
- %li{class: ("active" if params[:scope] == 'created-by-me')}
- = link_to project_filter_path(scope: 'created-by-me') do
- Created by me
- %span.pull-right
- = authored_entities_count(current_user, entity, @project)
-
- %fieldset
- %legend State
- %ul.nav.nav-pills
- %li{class: ("active" if params[:state] == 'opened')}
- = link_to project_filter_path(state: 'opened') do
- Open
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to project_filter_path(state: 'closed') do
- Closed
- %li{class: ("active" if params[:state] == 'all')}
- = link_to project_filter_path(state: 'all') do
- All
-
- - if defined?(labels)
- %fieldset
- %legend
- Labels
- %small.pull-right
- = link_to project_labels_path(@project), class: 'light' do
- %i.icon-edit
- %ul.nav.nav-pills.nav-stacked.nav-small.labels-filter
- - @project.labels.order_by_name.each do |label|
- %li{class: label_filter_class(label.name)}
- = link_to labels_filter_path(label.name) do
- = render_colored_label(label)
- - if selected_label?(label.name)
- .pull-right
- %i.icon-remove
-
- - if @project.labels.empty?
- .light-well
- Create first label at
- = link_to 'labels page', project_labels_path(@project)
- %br
- or #{link_to 'generate', generate_project_labels_path(@project, redirect: redirect), method: :post} default set of labels
-
- %fieldset
- - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any?
- = link_to project_entities_path, class: 'cgray pull-right' do
- %i.icon-remove
- %strong Clear filter
-
-
diff --git a/app/views/shared/_projects_list.html.haml b/app/views/shared/_projects_list.html.haml
new file mode 100644
index 0000000000..4c58092af4
--- /dev/null
+++ b/app/views/shared/_projects_list.html.haml
@@ -0,0 +1,17 @@
+- projects_limit = 20 unless local_assigns[:projects_limit]
+- avatar = true unless local_assigns[:avatar] == false
+- stars = false unless local_assigns[:stars] == true
+%ul.well-list.projects-list
+ - projects.each_with_index do |project, i|
+ %li{class: (i >= projects_limit) ? 'project-row hide' : 'project-row'}
+ = render "shared/project", project: project, avatar: avatar, stars: stars
+ - if projects.blank?
+ %li
+ .nothing-here-block There are no projects here.
+ - if projects.count > projects_limit
+ %li.bottom
+ %span.light
+ #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
+ %span
+ = link_to '#', class: 'js-expand' do
+ Show all
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
index 7dec48e658..3596aabe30 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,4 +1,5 @@
.gitlab-promo
- = link_to "Homepage", "https://www.gitlab.com/"
- = link_to "Blog", "https://www.gitlab.com/blog/"
- = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
+ = link_to 'Homepage', promo_url
+ = link_to "Blog", promo_url + '/blog/'
+ = link_to "@gitlab", "https://twitter.com/gitlab"
+ = link_to "Requests", "http://feedback.gitlab.com/"
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 4d9534f49b..eb2e1919e1 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,4 +1,4 @@
-= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
+= form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do
= select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm"
= hidden_field_tag :destination, destination
- if defined?(path)
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 7b37b39780..af3d35de32 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,22 +1,22 @@
.dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
- = @sort
+ = sort_options_hash[@sort]
- else
- Newest
+ = sort_title_recently_created
%b.caret
- %ul.dropdown-menu
+ %ul.dropdown-menu.dropdown-menu-align-right
%li
- = link_to project_filter_path(sort: 'newest') do
- Newest
- = link_to project_filter_path(sort: 'oldest') do
- Oldest
- = link_to project_filter_path(sort: 'recently_updated') do
- Recently updated
- = link_to project_filter_path(sort: 'last_updated') do
- Last updated
- = link_to project_filter_path(sort: 'milestone_due_soon') do
- Milestone due soon
- = link_to project_filter_path(sort: 'milestone_due_later') do
- Milestone due later
+ = link_to page_filter_path(sort: sort_value_recently_created) do
+ = sort_title_recently_created
+ = link_to page_filter_path(sort: sort_value_oldest_created) do
+ = sort_title_oldest_created
+ = link_to page_filter_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to page_filter_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
+ = link_to page_filter_path(sort: sort_value_milestone_soon) do
+ = sort_title_milestone_soon
+ = link_to page_filter_path(sort: sort_value_milestone_later) do
+ = sort_title_milestone_later
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 8cec6168ab..30458793fd 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -8,7 +8,7 @@
= render_markup(@snippet.file_name, @snippet.data)
- else
.file-content.code
- = render 'shared/file_hljs', blob: @snippet
+ = render 'shared/file_highlight', blob: @snippet
- else
.file-content.code
.nothing-here-block Empty file
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 49ea8460e7..4e0663ea20 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -10,22 +10,8 @@
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
- - unless @snippet.respond_to?(:project)
- .form-group
- = f.label "Access", class: 'control-label'
- .col-sm-10
- = f.label :private_true, class: 'radio-label' do
- = f.radio_button :private, true
- %span
- %strong Private
- (only you can see this snippet)
- %br
- = f.label :private_false, class: 'radio-label' do
- = f.radio_button :private, false
- %span
- %strong Public
- (GitLab users can see this snippet)
-
+ = render "shared/snippets/visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true
+
.form-group
.file-editor
= f.label :file_name, "File", class: 'control-label'
@@ -44,7 +30,7 @@
= f.submit 'Save', class: "btn-save btn"
- if @snippet.respond_to?(:project)
- = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
- else
= link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
diff --git a/app/views/shared/snippets/_visibility_level.html.haml b/app/views/shared/snippets/_visibility_level.html.haml
new file mode 100644
index 0000000000..9acff18e45
--- /dev/null
+++ b/app/views/shared/snippets/_visibility_level.html.haml
@@ -0,0 +1,27 @@
+.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")
+ .col-sm-10
+ - if can_change_visibility_level
+ - Gitlab::VisibilityLevel.values.each do |level|
+ .radio
+ - restricted = restricted_visibility_levels.include?(level)
+ = f.radio_button :visibility_level, level, disabled: restricted
+ = label "#{dom_class(@snippet)}_visibility_level", level do
+ = visibility_level_icon(level)
+ .option-title
+ = visibility_level_label(level)
+ .option-descr
+ = snippet_visibility_level_description(level)
+ - unless restricted_visibility_levels.empty?
+ .col-sm-10
+ %span.info
+ Some visibility level settings have been restricted by the administrator.
+ - else
+ .col-sm-10
+ %span.info
+ = visibility_level_icon(visibility_level)
+ %strong
+ = visibility_level_label(visibility_level)
+ .light= visibility_level_description(visibility_level)
diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml
index e6f8316733..5bb2866434 100644
--- a/app/views/snippets/_snippet.html.haml
+++ b/app/views/snippets/_snippet.html.haml
@@ -4,14 +4,14 @@
= truncate(snippet.title, length: 60)
- if snippet.private?
%span.label.label-gray
- %i.icon-lock
+ %i.fa.fa-lock
private
%span.cgray.monospace.tiny.pull-right
= snippet.file_name
%small.pull-right.cgray
- if snippet.project_id?
- = link_to snippet.project.name_with_namespace, project_path(snippet.project)
+ = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
.snippet-info
= "##{snippet.id}"
diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml
index e3edd85698..0df5ade500 100644
--- a/app/views/snippets/current_user_index.html.haml
+++ b/app/views/snippets/current_user_index.html.haml
@@ -1,5 +1,5 @@
%h3.page-title
- My Snippets
+ Your Snippets
.pull-right
= link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet
@@ -23,6 +23,11 @@
Private
%span.pull-right
= @user.snippets.are_private.count
+ = nav_tab :scope, 'are_internal' do
+ = link_to user_snippets_path(@user, scope: 'are_internal') do
+ Internal
+ %span.pull-right
+ = @user.snippets.are_internal.count
= nav_tab :scope, 'are_public' do
= link_to user_snippets_path(@user, scope: 'are_public') do
Public
diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml
index cea2517a8e..5cd8ae26cf 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -2,10 +2,12 @@
Public snippets
.pull-right
- = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
- Add new snippet
- = link_to user_snippets_path(current_user), class: "btn btn-grouped" do
- My snippets
+
+ - if current_user
+ = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
+ Add new snippet
+ = link_to user_snippets_path(current_user), class: "btn btn-grouped" do
+ Your snippets
%p.light
Public snippets created by you and other users are listed here
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 1d2e3d5ae4..5204fb9a90 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -3,7 +3,7 @@
- if @snippet.private?
%span.label.label-success
- %i.icon-lock
+ %i.fa.fa-lock
private
.pull-right
@@ -17,27 +17,27 @@
%span.light
by
= link_to user_snippets_path(@snippet.author) do
- = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
+ = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16", alt: ''
= @snippet.author_name
.back-link
- if @snippet.author == current_user
= link_to user_snippets_path(current_user) do
- ← my snippets
+ ← your snippets
- else
= link_to snippets_path do
← discover snippets
.file-holder
.file-title
- %i.icon-file
- %span.file_name
+ %i.fa.fa-file
+ %strong
= @snippet.file_name
- .options
+ .file-actions
.btn-group
- if can?(current_user, :modify_personal_snippet, @snippet)
- = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-small", title: 'Edit Snippet'
- = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-small", target: "_blank"
+ = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-sm", title: 'Edit Snippet'
+ = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
- if can?(current_user, :admin_personal_snippet, @snippet)
- = link_to "remove", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-small btn-remove", title: 'Delete Snippet'
+ = link_to "remove", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet'
= render 'shared/snippets/blob'
diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml
index 1cb53ec6a2..df524cd18b 100644
--- a/app/views/snippets/user_index.html.haml
+++ b/app/views/snippets/user_index.html.haml
@@ -4,8 +4,9 @@
%span
\/
Snippets
- = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
- Add new snippet
+ - if current_user
+ = link_to new_snippet_path, class: "btn btn-sm add_new pull-right", title: "New Snippet" do
+ Add new snippet
%hr
diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml
index 09b2985d49..f360fbb3d5 100644
--- a/app/views/users/_groups.html.haml
+++ b/app/views/users/_groups.html.haml
@@ -1,3 +1,4 @@
-- groups.each do |group|
- = link_to group, class: 'profile-groups-avatars', :title => group.name do
- = image_tag group_icon(group.path)
+.clearfix
+ - groups.each do |group|
+ = link_to group, class: 'profile-groups-avatars inline', title: group.name do
+ = image_tag group_icon(group), class: 'avatar group-avatar s40'
diff --git a/app/views/users/_profile.html.haml b/app/views/users/_profile.html.haml
index 3b44959baa..90d9980c85 100644
--- a/app/views/users/_profile.html.haml
+++ b/app/views/users/_profile.html.haml
@@ -5,6 +5,10 @@
%li
%span.light Member since
%strong= user.created_at.stamp("Aug 21, 2011")
+ - unless user.public_email.blank?
+ %li
+ %span.light E-mail:
+ %strong= link_to user.public_email, "mailto:#{user.public_email}"
- unless user.skype.blank?
%li
%span.light Skype:
@@ -12,7 +16,7 @@
- unless user.linkedin.blank?
%li
%span.light LinkedIn:
- %strong= user.linkedin
+ %strong= link_to user.linkedin, "http://www.linkedin.com/in/#{user.linkedin}"
- unless user.twitter.blank?
%li
%span.light Twitter:
@@ -21,7 +25,7 @@
%li
%span.light Website:
%strong= link_to user.short_website_url, user.full_website_url
- - unless user.bio.blank?
+ - unless user.location.blank?
%li
- %span.light Bio:
- %span= user.bio
+ %span.light Location:
+ %strong= user.location
diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml
index 1d38f8e8ab..297fa53739 100644
--- a/app/views/users/_projects.html.haml
+++ b/app/views/users/_projects.html.haml
@@ -1,6 +1,13 @@
-.panel.panel-default
- .panel-heading Personal projects
- %ul.well-list
- - projects.each do |project|
- %li
- = link_to_project project
+- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
+ .panel.panel-default.contributed-projects
+ .panel-heading Projects contributed to
+ = render 'shared/projects_list',
+ projects: contributed_projects.sort_by(&:star_count).reverse,
+ projects_limit: 5, stars: true, avatar: false
+
+- if local_assigns.has_key?(:projects) && projects.present?
+ .panel.panel-default
+ .panel-heading Personal projects
+ = render 'shared/projects_list',
+ projects: projects.sort_by(&:star_count).reverse,
+ projects_limit: 10, stars: true, avatar: false
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
new file mode 100644
index 0000000000..922b0c6ceb
--- /dev/null
+++ b/app/views/users/calendar.html.haml
@@ -0,0 +1,12 @@
+%h4
+ Contributions calendar
+ .pull-right
+ %small Issues, merge requests and push events
+#cal-heatmap.calendar
+ :javascript
+ new Calendar(
+ #{@timestamps.to_json},
+ #{@starting_year},
+ #{@starting_month},
+ '#{user_calendar_activities_path}'
+ );
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
new file mode 100644
index 0000000000..027a93a75f
--- /dev/null
+++ b/app/views/users/calendar_activities.html.haml
@@ -0,0 +1,23 @@
+%h4.prepend-top-20
+ %span.light Contributions for
+ %strong #{@calendar_date.to_s(:short)}
+
+%ul.bordered-list
+ - @events.sort_by(&:created_at).each do |event|
+ %li
+ %span.light
+ %i.fa.fa-clock-o
+ = event.created_at.to_s(:time)
+ - if event.push?
+ #{event.action_name} #{event.ref_type} #{event.ref_name}
+ - else
+ = event_action_name(event)
+ - if event.target
+ %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
+
+ at
+ %strong
+ - if event.project
+ = link_to_project event.project
+ - else
+ = event.project_name
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
new file mode 100644
index 0000000000..8fe30b2363
--- /dev/null
+++ b/app/views/users/show.atom.builder
@@ -0,0 +1,12 @@
+xml.instruct!
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
+ xml.title "Activity feed for #{@user.name}"
+ xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
+ xml.link href: user_url(@user), rel: "alternate", type: "text/html"
+ xml.id projects_url
+ xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+
+ @events.each do |event|
+ event_to_atom(xml, event)
+ end
+end
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 60159a29b9..9dd8cb0738 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,26 +1,50 @@
.row
- .col-md-8
- %h3.page-title
- = image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: ''
- = @user.name
- - if @user == current_user
- .pull-right
- = link_to profile_path, class: 'btn' do
- %i.icon-edit
- Edit Profile settings
- %br
- %span.user-show-username #{@user.username}
- %br
- %small member since #{@user.created_at.stamp("Nov 12, 2031")}
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
+ %section.col-md-8
+ .header-with-avatar
+ = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
+ %h3
+ = @user.name
+ - if @user == current_user
+ .pull-right
+ = link_to profile_path, class: 'btn btn-sm' do
+ %i.fa.fa-pencil-square-o
+ Edit Profile settings
+ .username
+ @#{@user.username}
+ .description
+ - if @user.bio.present?
+ = @user.bio
+
.clearfix
- if @groups.any?
- %h4 Groups:
- = render 'groups', groups: @groups
+ .prepend-top-20
+ %h4 Groups
+ = render 'groups', groups: @groups
+ %hr
+
+ .hidden-xs
+ .user-calendar
+ %h4.center.light
+ %i.fa.fa-spinner.fa-spin
+ .user-calendar-activities
%hr
- %h4 User Activity:
- = render @events
- .col-md-4
+ %h4
+ User Activity
+
+ - if current_user
+ %span.rss-icon.pull-right
+ = link_to user_path(@user, :atom, { private_token: current_user.private_token }) do
+ %strong
+ %i.fa.fa-rss
+
+ .content_list
+ = spinner
+ %aside.col-md-4
= render 'profile', user: @user
- - if @projects.present?
- = render 'projects', projects: @projects
+ = render 'projects', projects: @projects, contributed_projects: @contributed_projects
+
+:coffeescript
+ $(".user-calendar").load("#{user_calendar_path}")
diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml
deleted file mode 100644
index ad363eaba2..0000000000
--- a/app/views/users_groups/_users_group.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-- user = member.user
-- return unless user
-- show_roles = true if show_roles.nil?
-%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
- %span{class: ("list-item-name" if show_controls)}
- = image_tag avatar_icon(user.email, 16), class: "avatar s16"
- %strong= user.name
- %span.cgray= user.username
- - if user == current_user
- %span.label.label-success It's you
-
- - if show_roles
- %span.pull-right
- %strong= member.human_access
- - if show_controls
- - if can?(current_user, :modify, member)
- = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
- %i.icon-edit
- - if can?(current_user, :destroy, member)
- - if current_user == member.user
- = link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-minus.icon-white
- - else
- = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-minus.icon-white
-
- .edit-member.hide.js-toggle-content
- = form_for [@group, member], remote: true do |f|
- .alert.prepend-top-20
- = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access)
- = f.submit 'Save', class: 'btn btn-save btn-small'
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 788d9065a7..36ea674206 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,6 +1,10 @@
.votes.votes-block
- .progress
- .progress-bar.progress-bar-success{style: "width: #{votable.upvotes_in_percent}%;"}
- .progress-bar.progress-bar-danger{style: "width: #{votable.downvotes_in_percent}%;"}
- .upvotes= "#{votable.upvotes} up"
- .downvotes= "#{votable.downvotes} down"
+ .btn-group
+ - unless votable.upvotes.zero?
+ .btn.btn-sm.disabled.cgreen
+ %i.fa.fa-thumbs-up
+ = votable.upvotes
+ - unless votable.downvotes.zero?
+ .btn.btn-sm.disabled.cred
+ %i.fa.fa-thumbs-down
+ = votable.downvotes
diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml
index ee80547483..2cb3ae04e1 100644
--- a/app/views/votes/_votes_inline.html.haml
+++ b/app/views/votes/_votes_inline.html.haml
@@ -1,9 +1,9 @@
.votes.votes-inline
- unless votable.upvotes.zero?
- .upvotes
+ %span.upvotes.cgreen
+ #{votable.upvotes}
- unless votable.downvotes.zero?
\/
- unless votable.downvotes.zero?
- .downvotes
+ %span.downvotes.cred
\- #{votable.downvotes}
diff --git a/app/workers/auto_merge_worker.rb b/app/workers/auto_merge_worker.rb
new file mode 100644
index 0000000000..a6dd73eee5
--- /dev/null
+++ b/app/workers/auto_merge_worker.rb
@@ -0,0 +1,13 @@
+class AutoMergeWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(merge_request_id, current_user_id, params)
+ params = params.with_indifferent_access
+ current_user = User.find(current_user_id)
+ merge_request = MergeRequest.find(merge_request_id)
+ merge_request.should_remove_source_branch = params[:should_remove_source_branch]
+ merge_request.automerge!(current_user, params[:commit_message])
+ end
+end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 2947c8e3ec..1d21addece 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -1,25 +1,61 @@
class EmailsOnPushWorker
include Sidekiq::Worker
- def perform(project_id, recipients, push_data)
+ def perform(project_id, recipients, push_data, options = {})
+ options.symbolize_keys!
+ options.reverse_merge!(
+ send_from_committer_email: false,
+ disable_diffs: false
+ )
+ send_from_committer_email = options[:send_from_committer_email]
+ disable_diffs = options[:disable_diffs]
+
project = Project.find(project_id)
before_sha = push_data["before"]
after_sha = push_data["after"]
- branch = push_data["ref"]
+ ref = push_data["ref"]
author_id = push_data["user_id"]
- if before_sha =~ /^000000/ || after_sha =~ /^000000/
- # skip if new branch was pushed or branch was removed
- return true
+ action =
+ if Gitlab::Git.blank_ref?(before_sha)
+ :create
+ elsif Gitlab::Git.blank_ref?(after_sha)
+ :delete
+ else
+ :push
+ end
+
+ compare = nil
+ reverse_compare = false
+ if action == :push
+ compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
+
+ return false if compare.same
+
+ if compare.commits.empty?
+ compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha)
+
+ reverse_compare = true
+
+ return false if compare.commits.empty?
+ end
end
- compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
-
- # Do not send emails if git compare failed
- return false unless compare && compare.commits.present?
-
recipients.split(" ").each do |recipient|
- Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver
+ Notify.repository_push_email(
+ project_id,
+ recipient,
+ author_id: author_id,
+ ref: ref,
+ action: action,
+ compare: compare,
+ reverse_compare: reverse_compare,
+ send_from_committer_email: send_from_committer_email,
+ disable_diffs: disable_diffs
+ ).deliver
end
+ ensure
+ compare = nil
+ GC.start
end
end
diff --git a/app/workers/fork_registration_worker.rb b/app/workers/fork_registration_worker.rb
new file mode 100644
index 0000000000..fffa8b3a65
--- /dev/null
+++ b/app/workers/fork_registration_worker.rb
@@ -0,0 +1,12 @@
+class ForkRegistrationWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(from_project_id, to_project_id, private_token)
+ from_project = Project.find(from_project_id)
+ to_project = Project.find(to_project_id)
+
+ from_project.gitlab_ci_service.fork_registration(to_project, private_token)
+ end
+end
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
new file mode 100644
index 0000000000..8b50f42398
--- /dev/null
+++ b/app/workers/irker_worker.rb
@@ -0,0 +1,169 @@
+require 'json'
+require 'socket'
+
+class IrkerWorker
+ include Sidekiq::Worker
+
+ def perform(project_id, chans, colors, push_data, settings)
+ project = Project.find(project_id)
+
+ # Get config parameters
+ return false unless init_perform settings, chans, colors
+
+ repo_name = push_data['repository']['name']
+ committer = push_data['user_name']
+ branch = push_data['ref'].gsub(%r'refs/[^/]*/', '')
+
+ if @colors
+ repo_name = "\x0304#{repo_name}\x0f"
+ branch = "\x0305#{branch}\x0f"
+ end
+
+ # Firsts messages are for branch creation/deletion
+ send_branch_updates push_data, project, repo_name, committer, branch
+
+ # Next messages are for commits
+ send_commits push_data, project, repo_name, committer, branch
+
+ close_connection
+ true
+ end
+
+ private
+
+ def init_perform(set, chans, colors)
+ @colors = colors
+ @channels = chans
+ start_connection set['server_ip'], set['server_port']
+ end
+
+ def start_connection(irker_server, irker_port)
+ begin
+ @socket = TCPSocket.new irker_server, irker_port
+ rescue Errno::ECONNREFUSED => e
+ logger.fatal "Can't connect to Irker daemon: #{e}"
+ return false
+ end
+ true
+ end
+
+ def sendtoirker(privmsg)
+ to_send = { to: @channels, privmsg: privmsg }
+ @socket.puts JSON.dump(to_send)
+ end
+
+ def close_connection
+ @socket.close
+ end
+
+ def send_branch_updates(push_data, project, repo_name, committer, branch)
+ if Gitlab::Git.blank_ref?(push_data['before'])
+ send_new_branch project, repo_name, committer, branch
+ elsif Gitlab::Git.blank_ref?(push_data['after'])
+ send_del_branch repo_name, committer, branch
+ end
+ end
+
+ def send_new_branch(project, repo_name, committer, branch)
+ repo_path = project.path_with_namespace
+ newbranch = "#{Gitlab.config.gitlab.url}/#{repo_path}/branches"
+ newbranch = "\x0302\x1f#{newbranch}\x0f" if @colors
+
+ privmsg = "[#{repo_name}] #{committer} has created a new branch "
+ privmsg += "#{branch}: #{newbranch}"
+ sendtoirker privmsg
+ end
+
+ def send_del_branch(repo_name, committer, branch)
+ privmsg = "[#{repo_name}] #{committer} has deleted the branch #{branch}"
+ sendtoirker privmsg
+ end
+
+ def send_commits(push_data, project, repo_name, committer, branch)
+ return if push_data['total_commits_count'] == 0
+
+ # Next message is for number of commit pushed, if any
+ if Gitlab::Git.blank_ref?(push_data['before'])
+ # Tweak on push_data["before"] in order to have a nice compare URL
+ push_data['before'] = before_on_new_branch push_data, project
+ end
+
+ send_commits_count(push_data, project, repo_name, committer, branch)
+
+ # One message per commit, limited by 3 messages (same limit as the
+ # github irc hook)
+ commits = push_data['commits'].first(3)
+ commits.each do |hook_attrs|
+ send_one_commit project, hook_attrs, repo_name, branch
+ end
+ end
+
+ def before_on_new_branch(push_data, project)
+ commit = commit_from_id project, push_data['commits'][0]['id']
+ parents = commit.parents
+ # Return old value if there's no new one
+ return push_data['before'] if parents.empty?
+ # Or return the first parent-commit
+ parents[0].id
+ end
+
+ def send_commits_count(data, project, repo, committer, branch)
+ url = compare_url data, project.path_with_namespace
+ commits = colorize_commits data['total_commits_count']
+
+ new_commits = 'new commit'
+ new_commits += 's' if data['total_commits_count'] > 1
+
+ sendtoirker "[#{repo}] #{committer} pushed #{commits} #{new_commits} " \
+ "to #{branch}: #{url}"
+ end
+
+ def compare_url(data, repo_path)
+ sha1 = Commit::truncate_sha(data['before'])
+ sha2 = Commit::truncate_sha(data['after'])
+ compare_url = "#{Gitlab.config.gitlab.url}/#{repo_path}/compare"
+ compare_url += "/#{sha1}...#{sha2}"
+ colorize_url compare_url
+ end
+
+ def send_one_commit(project, hook_attrs, repo_name, branch)
+ commit = commit_from_id project, hook_attrs['id']
+ sha = colorize_sha Commit::truncate_sha(hook_attrs['id'])
+ author = hook_attrs['author']['name']
+ files = colorize_nb_files(files_count commit)
+ title = commit.title
+
+ sendtoirker "#{repo_name}/#{branch} #{sha} #{author} (#{files}): #{title}"
+ end
+
+ def commit_from_id(project, id)
+ commit = Gitlab::Git::Commit.find(project.repository, id)
+ Commit.new(commit)
+ end
+
+ def files_count(commit)
+ files = "#{commit.diffs.count} file"
+ files += 's' if commit.diffs.count > 1
+ files
+ end
+
+ def colorize_sha(sha)
+ sha = "\x0314#{sha}\x0f" if @colors
+ sha
+ end
+
+ def colorize_nb_files(nb_files)
+ nb_files = "\x0312#{nb_files}\x0f" if @colors
+ nb_files
+ end
+
+ def colorize_url(url)
+ url = "\x0302\x1f#{url}\x0f" if @colors
+ url
+ end
+
+ def colorize_commits(commits)
+ commits = "\x02#{commits}\x0f" if @colors
+ commits
+ end
+end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index f110e20bf0..33d8cc8861 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -4,16 +4,15 @@ class PostReceive
sidekiq_options queue: :post_receive
- def perform(repo_path, oldrev, newrev, ref, identifier)
-
+ def perform(repo_path, identifier, changes)
if repo_path.start_with?(Gitlab.config.gitlab_shell.repos_path.to_s)
repo_path.gsub!(Gitlab.config.gitlab_shell.repos_path.to_s, "")
else
log("Check gitlab.yml config for correct gitlab_shell.repos_path variable. \"#{Gitlab.config.gitlab_shell.repos_path}\" does not match \"#{repo_path}\"")
end
- repo_path.gsub!(/\.git$/, "")
- repo_path.gsub!(/^\//, "")
+ repo_path.gsub!(/\.git\z/, "")
+ repo_path.gsub!(/\A\//, "")
project = Project.find_with_namespace(repo_path)
@@ -22,27 +21,42 @@ class PostReceive
return false
end
- user = identify(identifier, project, newrev)
+ changes = Base64.decode64(changes) unless changes.include?(" ")
+ changes = utf8_encode_changes(changes)
+ changes = changes.lines
- unless user
- log("Triggered hook for non-existing user \"#{identifier} \"")
- return false
- end
+ changes.each do |change|
+ oldrev, newrev, ref = change.strip.split(' ')
- if tag?(ref)
- GitTagPushService.new.execute(project, user, oldrev, newrev, ref)
- else
- GitPushService.new.execute(project, user, oldrev, newrev, ref)
+ @user ||= identify(identifier, project, newrev)
+
+ unless @user
+ log("Triggered hook for non-existing user \"#{identifier} \"")
+ return false
+ end
+
+ if Gitlab::Git.tag_ref?(ref)
+ GitTagPushService.new.execute(project, @user, oldrev, newrev, ref)
+ else
+ GitPushService.new.execute(project, @user, oldrev, newrev, ref)
+ end
end
end
+ def utf8_encode_changes(changes)
+ changes = changes.dup
+
+ changes.force_encoding("UTF-8")
+ return changes if changes.valid_encoding?
+
+ # Convert non-UTF-8 branch/tag names to UTF-8 so they can be dumped as JSON.
+ detection = CharlockHolmes::EncodingDetector.detect(changes)
+ return changes unless detection && detection[:encoding]
+
+ CharlockHolmes::Converter.convert(changes, detection[:encoding], 'UTF-8')
+ end
+
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
-
- private
-
- def tag?(ref)
- !!(/refs\/tags\/(.*)/.match(ref))
- end
end
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
new file mode 100644
index 0000000000..64d39c4d3f
--- /dev/null
+++ b/app/workers/project_service_worker.rb
@@ -0,0 +1,10 @@
+class ProjectServiceWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :project_web_hook
+
+ def perform(hook_id, data)
+ data = data.with_indifferent_access
+ Service.find(hook_id).execute(data)
+ end
+end
diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb
index 9f9b9b1df5..73085c046b 100644
--- a/app/workers/project_web_hook_worker.rb
+++ b/app/workers/project_web_hook_worker.rb
@@ -4,6 +4,7 @@ class ProjectWebHookWorker
sidekiq_options queue: :project_web_hook
def perform(hook_id, data)
- WebHook.find(hook_id).execute data
+ data = data.with_indifferent_access
+ WebHook.find(hook_id).execute(data)
end
end
diff --git a/app/workers/repository_archive_worker.rb b/app/workers/repository_archive_worker.rb
new file mode 100644
index 0000000000..021c113956
--- /dev/null
+++ b/app/workers/repository_archive_worker.rb
@@ -0,0 +1,43 @@
+class RepositoryArchiveWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :archive_repo
+
+ attr_accessor :project, :ref, :format
+
+ def perform(project_id, ref, format)
+ @project = Project.find(project_id)
+ @ref, @format = ref, format.downcase
+
+ repository = project.repository
+
+ repository.clean_old_archives
+
+ return unless file_path
+ return if archived? || archiving?
+
+ repository.archive_repo(ref, storage_path, format)
+ end
+
+ private
+
+ def storage_path
+ Gitlab.config.gitlab.repository_downloads_path
+ end
+
+ def file_path
+ @file_path ||= project.repository.archive_file_path(ref, storage_path, format)
+ end
+
+ def pid_file_path
+ @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
+ end
+
+ def archived?
+ File.exist?(file_path)
+ end
+
+ def archiving?
+ File.exist?(pid_file_path)
+ end
+end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 43ef54631a..e6a50afedb 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -6,17 +6,29 @@ class RepositoryImportWorker
def perform(project_id)
project = Project.find(project_id)
- result = gitlab_shell.send(:import_repository,
+
+ import_result = gitlab_shell.send(:import_repository,
project.path_with_namespace,
project.import_url)
+ return project.import_fail unless import_result
- if result
- project.import_finish
- project.save
- project.satellite.create unless project.satellite.exists?
- project.update_repository_size
- else
- project.import_fail
- end
+ data_import_result = if project.import_type == 'github'
+ Gitlab::GithubImport::Importer.new(project).execute
+ elsif project.import_type == 'gitlab'
+ Gitlab::GitlabImport::Importer.new(project).execute
+ elsif project.import_type == 'bitbucket'
+ Gitlab::BitbucketImport::Importer.new(project).execute
+ elsif project.import_type == 'google_code'
+ Gitlab::GoogleCodeImport::Importer.new(project).execute
+ else
+ true
+ end
+ return project.import_fail unless data_import_result
+
+ project.import_finish
+ project.save
+ project.satellite.create unless project.satellite.exists?
+ project.update_repository_size
+ Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end
-end
\ No newline at end of file
+end
diff --git a/bin/background_jobs b/bin/background_jobs
index c7ba4398cf..a041a4b043 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/bin/sh
cd $(dirname $0)/..
app_root=$(pwd)
@@ -6,22 +6,22 @@ sidekiq_pidfile="$app_root/tmp/pids/sidekiq.pid"
sidekiq_logfile="$app_root/log/sidekiq.log"
gitlab_user=$(ls -l config.ru | awk '{print $3}')
-function warn
+warn()
{
echo "$@" 1>&2
}
-function stop
+stop()
{
bundle exec sidekiqctl stop $sidekiq_pidfile >> $sidekiq_logfile 2>&1
}
-function killall
+killall()
{
pkill -u $gitlab_user -f 'sidekiq [0-9]'
}
-function restart
+restart()
{
if [ -f $sidekiq_pidfile ]; then
stop
@@ -30,20 +30,20 @@ function restart
start_sidekiq -d -L $sidekiq_logfile
}
-function start_no_deamonize
+start_no_deamonize()
{
start_sidekiq
}
-function start_sidekiq
+start_sidekiq()
{
- bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
+ bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
}
-function load_ok
+load_ok()
{
sidekiq_pid=$(cat $sidekiq_pidfile)
- if [[ -z $sidekiq_pid ]] ; then
+ if [ -z "$sidekiq_pid" ] ; then
warn "Could not find a PID in $sidekiq_pidfile"
exit 0
fi
diff --git a/bin/guard b/bin/guard
new file mode 100755
index 0000000000..0c1a532bd0
--- /dev/null
+++ b/bin/guard
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+#
+# This file was generated by Bundler.
+#
+# The application 'guard' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('guard', 'guard')
diff --git a/bin/pkgr_before_precompile.sh b/bin/pkgr_before_precompile.sh
index 283abb6a0c..5a2007f4ab 100755
--- a/bin/pkgr_before_precompile.sh
+++ b/bin/pkgr_before_precompile.sh
@@ -18,6 +18,3 @@ rm config/resque.yml
# Set default unicorn.rb file
echo "" > config/unicorn.rb
-
-# Required for assets precompilation
-sudo service postgresql start
diff --git a/bin/rspec b/bin/rspec
index 41e37089ac..20060ebd79 100755
--- a/bin/rspec
+++ b/bin/rspec
@@ -4,4 +4,4 @@ begin
rescue LoadError
end
require 'bundler/setup'
-load Gem.bin_path('rspec', 'rspec')
+load Gem.bin_path('rspec-core', 'rspec')
diff --git a/bin/web b/bin/web
index 1ad3b5d24b..67f236eb0b 100755
--- a/bin/web
+++ b/bin/web
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/bin/sh
cd $(dirname $0)/..
app_root=$(pwd)
@@ -6,28 +6,28 @@ app_root=$(pwd)
unicorn_pidfile="$app_root/tmp/pids/unicorn.pid"
unicorn_config="$app_root/config/unicorn.rb"
-function get_unicorn_pid
+get_unicorn_pid()
{
local pid=$(cat $unicorn_pidfile)
- if [ -z $pid ] ; then
+ if [ -z "$pid" ] ; then
echo "Could not find a PID in $unicorn_pidfile"
exit 1
fi
unicorn_pid=$pid
}
-function start
+start()
{
bundle exec unicorn_rails -D -c $unicorn_config -E $RAILS_ENV
}
-function stop
+stop()
{
get_unicorn_pid
kill -QUIT $unicorn_pid
}
-function reload
+reload()
{
get_unicorn_pid
kill -USR2 $unicorn_pid
diff --git a/config/application.rb b/config/application.rb
index 58a5949c65..fa399533e5 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -2,7 +2,7 @@ require File.expand_path('../boot', __FILE__)
require 'rails/all'
require 'devise'
-
+I18n.config.enforce_available_locales = false
Bundler.require(:default, Rails.env)
module Gitlab
@@ -12,16 +12,16 @@ module Gitlab
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/finders #{config.root}/app/models/concerns #{config.root}/app/models/project_services)
+ config.autoload_paths.push(*%W(#{config.root}/lib
+ #{config.root}/app/models/hooks
+ #{config.root}/app/models/concerns
+ #{config.root}/app/models/project_services
+ #{config.root}/app/models/members))
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
- # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
- # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
- # config.time_zone = 'Central Time (US & Canada)'
-
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
@@ -31,7 +31,7 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
- config.filter_parameters += [:password]
+ config.filter_parameters.push(:password, :password_confirmation, :private_token)
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
@@ -50,6 +50,8 @@ module Gitlab
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
+ config.action_view.sanitized_allowed_protocols = %w(smb)
+
# Relative url support
# Uncomment and customize the last line to run in a non-root path
# WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this.
@@ -70,8 +72,33 @@ module Gitlab
config.middleware.use Rack::Cors do
allow do
origins '*'
- resource '/api/*', headers: :any, methods: [:get, :post, :options, :put, :delete]
+ resource '/api/*',
+ headers: :any,
+ methods: [:get, :post, :options, :put, :delete],
+ expose: ['Link']
end
end
+
+ # Use Redis caching across all environments
+ redis_config_file = Rails.root.join('config', 'resque.yml')
+
+ redis_url_string = if File.exists?(redis_config_file)
+ YAML.load_file(redis_config_file)[Rails.env]
+ else
+ "redis://localhost:6379"
+ end
+
+ # Redis::Store does not handle Unix sockets well, so let's do it for them
+ redis_config_hash = Redis::Store::Factory.extract_host_options_from_uri(redis_url_string)
+ redis_uri = URI.parse(redis_url_string)
+ if redis_uri.scheme == 'unix'
+ redis_config_hash[:path] = redis_uri.path
+ end
+
+ redis_config_hash[:namespace] = 'cache:gitlab'
+ config.cache_store = :redis_store, redis_config_hash
+
+ # This is needed for gitlab-shell
+ ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
end
end
diff --git a/config/database.yml.mysql b/config/database.yml.mysql
index 55ac088bc1..a99c50706c 100644
--- a/config/database.yml.mysql
+++ b/config/database.yml.mysql
@@ -4,6 +4,7 @@
production:
adapter: mysql2
encoding: utf8
+ collation: utf8_general_ci
reconnect: false
database: gitlabhq_production
pool: 10
@@ -18,6 +19,7 @@ production:
development:
adapter: mysql2
encoding: utf8
+ collation: utf8_general_ci
reconnect: false
database: gitlabhq_development
pool: 5
@@ -31,6 +33,7 @@ development:
test: &test
adapter: mysql2
encoding: utf8
+ collation: utf8_general_ci
reconnect: false
database: gitlabhq_test
pool: 5
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 2450d5719e..3316ece387 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -11,8 +11,9 @@ Gitlab::Application.configure do
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = false
- # Compress JavaScripts and CSS
- config.assets.compress = true
+ # Compress JavaScripts and CSS.
+ config.assets.js_compressor = :uglifier
+ # config.assets.css_compressor = :sass
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = true
@@ -45,16 +46,6 @@ Gitlab::Application.configure do
# Use a different logger for distributed setups
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
- # Use a different cache store in production
- config_file = Rails.root.join('config', 'resque.yml')
-
- resque_url = if File.exists?(config_file)
- YAML.load_file(config_file)[Rails.env]
- else
- "redis://localhost:6379"
- end
- config.cache_store = :redis_store, resque_url, {namespace: 'cache:gitlab'}
-
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
@@ -84,7 +75,6 @@ Gitlab::Application.configure do
config.action_mailer.raise_delivery_errors = true
config.eager_load = true
- config.assets.js_compressor = :uglifier
config.allow_concurrency = false
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 25b082b98d..2d5e7addcd 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -5,7 +5,7 @@ Gitlab::Application.configure do
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
- config.cache_classes = true
+ config.cache_classes = false
# Configure static asset server for tests with Cache-Control for performance
config.serve_static_assets = true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index d897eb4c02..ba40671b16 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -2,10 +2,17 @@
# GitLab application config file #
# # # # # # # # # # # # # # # # # #
#
+########################### NOTE #####################################
+# This file should not receive new settings. All configuration options #
+# are being moved to ApplicationSetting model! #
+########################################################################
+#
# How to use:
-# 1. copy file as gitlab.yml
-# 2. Replace gitlab -> host with your domain
-# 3. Replace gitlab -> email_from
+# 1. Copy file as gitlab.yml
+# 2. Update gitlab -> host with your fully qualified domain name
+# 3. Update gitlab -> email_from
+# 4. If you installed Git from source, change git -> bin_path to /usr/local/bin/git
+# 5. Review this configuration file for other settings you may want to adjust
production: &base
#
@@ -16,8 +23,8 @@ production: &base
gitlab:
## Web server settings (note: host is the FQDN, do not include http://)
host: localhost
- port: 80
- https: false
+ port: 80 # Set to 443 if using HTTPS, see installation.md#using-https for additional HTTPS configuration details
+ https: false # Set to true if using HTTPS, see installation.md#using-https for additional HTTPS configuration details
# Uncommment this line below if your ssh host is different from HTTP/HTTPS one
# (you'd obviously need to replace ssh.host_example.com with your own host).
@@ -31,14 +38,21 @@ production: &base
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
# user: git
+ ## Date & Time settings
+ # Uncomment and customize if you want to change the default time zone of GitLab application.
+ # To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production`
+ # time_zone: 'UTC'
+
## Email settings
+ # Uncomment and set to false if you need to disable email sending from GitLab (default: true)
+ # email_enabled: true
# Email address used in the "From" field in mails sent by GitLab
email_from: example@example.com
+ email_display_name: GitLab
+ email_reply_to: noreply@example.com
- # Email server smtp settings are in [a separate file](initializers/smtp_settings.rb.sample).
+ # Email server smtp settings are in config/initializers/smtp_settings.rb.sample
- ## User settings
- default_projects_limit: 10
# default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace
## Default theme
@@ -49,26 +63,12 @@ production: &base
## COLOR = 5
# default_theme: 2 # default: 2
- ## Users can create accounts
- # This also allows normal users to sign up for accounts themselves
- # default: false - By default GitLab administrators must create all new accounts
- # signup_enabled: true
-
- ## Standard login settings
- # The standard login can be disabled to force login via LDAP
- # default: true - If set to false the standard login form won't be shown on the sign-in page
- # signin_enabled: false
-
- # Restrict setting visibility levels for non-admin users.
- # The default is to allow all levels.
- #restricted_visibility_levels: [ "public" ]
-
## Automatic issue closing
# If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
# This happens when the commit is pushed or merged into the default branch of a project.
# When not specified the default issue_closing_pattern as specified below will be used.
- # Tip: you can test your closing pattern at http://rubular.com
- # issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)'
+ # Tip: you can test your closing pattern at http://rubular.com.
+ # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)'
## Default project features settings
default_projects_features:
@@ -78,41 +78,17 @@ production: &base
snippets: false
visibility_level: "private" # can be "private" | "internal" | "public"
+ ## Webhook settings
+ # Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10)
+ # webhook_timeout: 10
+
## Repository downloads directory
# When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory.
# The default is 'tmp/repositories' relative to the root of the Rails app.
# repository_downloads_path: tmp/repositories
- ## External issues trackers
- issues_tracker:
- # redmine:
- # title: "Redmine"
- # ## If not nil, link 'Issues' on project page will be replaced with this
- # ## Use placeholders:
- # ## :project_id - GitLab project identifier
- # ## :issues_tracker_id - Project Name or Id in external issue tracker
- # project_url: "http://redmine.sample/projects/:issues_tracker_id"
- #
- # ## If not nil, links from /#\d/ entities from commit messages will replaced with this
- # ## Use placeholders:
- # ## :project_id - GitLab project identifier
- # ## :issues_tracker_id - Project Name or Id in external issue tracker
- # ## :id - Issue id (from commit messages)
- # issues_url: "http://redmine.sample/issues/:id"
- #
- # ## If not nil, links to creating new issues will be replaced with this
- # ## Use placeholders:
- # ## :project_id - GitLab project identifier
- # ## :issues_tracker_id - Project Name or Id in external issue tracker
- # new_issue_url: "http://redmine.sample/projects/:issues_tracker_id/issues/new"
- #
- # jira:
- # title: "Atlassian Jira"
- # project_url: "http://jira.sample/issues/?jql=project=:issues_tracker_id"
- # issues_url: "http://jira.sample/browse/:id"
- # new_issue_url: "http://jira.sample/secure/CreateIssue.jspa"
-
## Gravatar
+ ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar:
enabled: true # Use user avatar image from Gravatar.com (default: true)
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
@@ -128,35 +104,75 @@ production: &base
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
- host: '_your_ldap_server'
- port: 636
- uid: 'sAMAccountName'
- method: 'ssl' # "tls" or "ssl" or "plain"
- bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
- password: '_the_password_of_the_bind_user'
- # If allow_username_or_email_login is enabled, GitLab will ignore everything
- # after the first '@' in the LDAP username submitted by the user on login.
- #
- # Example:
- # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
- # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
- #
- # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
- # disable this setting, because the userPrincipalName contains an '@'.
- allow_username_or_email_login: true
+ servers:
+ ##########################################################################
+ #
+ # Since GitLab 7.4, LDAP servers get ID's (below the ID is 'main'). GitLab
+ # Enterprise Edition now supports connecting to multiple LDAP servers.
+ #
+ # If you are updating from the old (pre-7.4) syntax, you MUST give your
+ # old server the ID 'main'.
+ #
+ ##########################################################################
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
- # Base where we can search for users
- #
- # Ex. ou=People,dc=gitlab,dc=example
- #
- base: ''
+ host: '_your_ldap_server'
+ port: 389
+ uid: 'sAMAccountName'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
- # Filter LDAP users
- #
- # Format: RFC 4515
- # Ex. (employeeType=developer)
- #
- user_filter: ''
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # To maintain tight control over the number of active users on your GitLab installation,
+ # enable this setting to keep new users blocked until they have been cleared by the admin
+ # (default: false).
+ block_auto_created_users: false
+
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+ # GitLab EE only: add more LDAP servers
+ # Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+ # so that GitLab can remember which LDAP server a user belongs to.
+ # uswest2:
+ # label:
+ # host:
+ # ....
## OmniAuth settings
@@ -179,14 +195,19 @@ production: &base
# arguments, followed by optional 'args' which can be either a hash or an array.
# Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
providers:
- # - { name: 'google_oauth2', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET',
+ # - { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET',
# args: { access_type: 'offline', approval_prompt: '' } }
- # - { name: 'twitter', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET'}
- # - { name: 'github', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET',
+ # - { name: 'twitter', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET'}
+ # - { name: 'github', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET',
# args: { scope: 'user:email' } }
+ # - { name: 'gitlab', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET',
+ # args: { scope: 'api' } }
+ # - { name: 'bitbucket', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET'}
@@ -204,6 +225,15 @@ production: &base
backup:
path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
# keep_time: 604800 # default: 0 (forever) (in seconds)
+ # upload:
+ # # Fog storage connection settings, see http://fog.io/storage/ .
+ # connection:
+ # provider: AWS
+ # region: eu-west-1
+ # aws_access_key_id: AKIAKIAKI
+ # aws_secret_access_key: 'secret123'
+ # # The remote 'directory' to store your backups. For S3, this would be the bucket name.
+ # remote_directory: 'my.s3.bucket'
## GitLab Shell settings
gitlab_shell:
@@ -244,10 +274,22 @@ production: &base
# piwik_url: '_your_piwik_url'
# piwik_site_id: '_your_piwik_site_id'
- ## Text under sign-in page (Markdown enabled)
- # sign_in_text: |
- # ![Company Logo](http://www.companydomain.com/logo.png)
- # [Learn more about CompanyName](http://www.companydomain.com/)
+ rack_attack:
+ git_basic_auth:
+ # Rack Attack IP banning enabled
+ # enabled: true
+ #
+ # Whitelist requests from 127.0.0.1 for web proxies (NGINX/Apache) with incorrect headers
+ # ip_whitelist: ["127.0.0.1"]
+ #
+ # Limit the number of Git HTTP authentication attempts per IP
+ # maxretry: 10
+ #
+ # Reset the auth attempt counter per IP after 60 seconds
+ # findtime: 60
+ #
+ # Ban an IP for one hour (3600s) after too many auth attempts
+ # bantime: 3600
development:
<<: *base
@@ -261,9 +303,9 @@ test:
port: 80
# When you run tests we clone and setup gitlab-shell
- # In order to setup it correctly you need to specify
+ # In order to setup it correctly you need to specify
# your system username you use to run GitLab
- # user: YOUR_USERNAME
+ # user: YOUR_USERNAME
satellites:
path: tmp/tests/gitlab-satellites/
gitlab_shell:
@@ -276,6 +318,20 @@ test:
project_url: "http://redmine/projects/:issues_tracker_id"
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
+ ldap:
+ enabled: false
+ servers:
+ main:
+ label: ldap
+ host: 127.0.0.1
+ port: 3890
+ uid: 'uid'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ base: 'dc=example,dc=com'
+ user_filter: ''
+ group_base: 'ou=groups,dc=example,dc=com'
+ admin_group: ''
+ sync_ssh_keys: false
staging:
<<: *base
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 136622c65a..0abd34fc3e 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -1,3 +1,5 @@
+require 'gitlab' # Load lib/gitlab.rb as soon as possible
+
class Settings < Settingslogic
source ENV.fetch('GITLAB_CONFIG') { "#{Rails.root}/config/gitlab.yml" }
namespace Rails.env
@@ -13,7 +15,11 @@ class Settings < Settingslogic
if gitlab_shell.ssh_port != 22
"ssh://#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:#{gitlab_shell.ssh_port}/"
else
- "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:"
+ if gitlab_shell.ssh_host.include? ':'
+ "[#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}]:"
+ else
+ "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:"
+ end
end
end
@@ -56,7 +62,27 @@ end
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
-Settings.ldap['allow_username_or_email_login'] = false if Settings.ldap['allow_username_or_email_login'].nil?
+
+# backwards compatibility, we only have one host
+if Settings.ldap['enabled'] || Rails.env.test?
+ if Settings.ldap['host'].present?
+ # We detected old LDAP configuration syntax. Update the config to make it
+ # look like it was entered with the new syntax.
+ server = Settings.ldap.except('sync_time')
+ Settings.ldap['servers'] = {
+ 'main' => server
+ }
+ end
+
+ Settings.ldap['servers'].each do |key, server|
+ server['label'] ||= 'LDAP'
+ server['block_auto_created_users'] = false if server['block_auto_created_users'].nil?
+ server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
+ server['active_directory'] = true if server['active_directory'].nil?
+ server['provider_name'] ||= "ldap#{key}".downcase
+ server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
+ end
+end
Settings['omniauth'] ||= Settingslogic.new({})
@@ -70,6 +96,7 @@ Settings['issues_tracker'] ||= {}
#
Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 10
+Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= 'localhost'
@@ -78,7 +105,10 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
+Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
+Settings.gitlab['email_display_name'] ||= "GitLab"
+Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}"
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
Settings.gitlab['user_home'] ||= begin
@@ -86,12 +116,16 @@ Settings.gitlab['user_home'] ||= begin
rescue ArgumentError # no user configured
'/home/' + Settings.gitlab['user']
end
-Settings.gitlab['signup_enabled'] ||= false
+Settings.gitlab['time_zone'] ||= nil
+Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
+Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
+Settings.gitlab['webhook_timeout'] ||= 10
+Settings.gitlab['max_attachment_size'] ||= 10
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
@@ -128,6 +162,11 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s
Settings['backup'] ||= Settingslogic.new({})
Settings.backup['keep_time'] ||= 0
Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root)
+Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
+# Convert upload connection settings to use symbol keys, to make Fog happy
+if Settings.backup['upload']['connection']
+ Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }]
+end
#
# Git
@@ -146,6 +185,17 @@ Settings.satellites['timeout'] ||= 30
#
Settings['extra'] ||= Settingslogic.new({})
+#
+# Rack::Attack settings
+#
+Settings['rack_attack'] ||= Settingslogic.new({})
+Settings.rack_attack['git_basic_auth'] ||= Settingslogic.new({})
+Settings.rack_attack.git_basic_auth['enabled'] = true if Settings.rack_attack.git_basic_auth['enabled'].nil?
+Settings.rack_attack.git_basic_auth['ip_whitelist'] ||= %w{127.0.0.1}
+Settings.rack_attack.git_basic_auth['maxretry'] ||= 10
+Settings.rack_attack.git_basic_auth['findtime'] ||= 1.minute
+Settings.rack_attack.git_basic_auth['bantime'] ||= 1.hour
+
#
# Testing settings
#
diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb
index 655590dff0..688cdf5f4b 100644
--- a/config/initializers/2_app.rb
+++ b/config/initializers/2_app.rb
@@ -6,8 +6,3 @@ module Gitlab
Settings
end
end
-
-#
-# Load all libs for threadsafety
-#
-Dir["#{Rails.root}/lib/**/*.rb"].each { |file| require file }
diff --git a/config/initializers/4_sidekiq.rb b/config/initializers/4_sidekiq.rb
index 228b14cb52..e856499732 100644
--- a/config/initializers/4_sidekiq.rb
+++ b/config/initializers/4_sidekiq.rb
@@ -14,7 +14,8 @@ Sidekiq.configure_server do |config|
}
config.server_middleware do |chain|
- chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger
+ chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
+ chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
end
end
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
index 7c2e7f3900..80d641d73a 100644
--- a/config/initializers/5_backend.rb
+++ b/config/initializers/5_backend.rb
@@ -6,3 +6,10 @@ require Rails.root.join("lib", "gitlab", "backend", "shell")
# GitLab shell adapter
require Rails.root.join("lib", "gitlab", "backend", "shell_adapter")
+
+required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
+current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
+
+unless current_version.valid? && required_version <= current_version
+ warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell."
+end
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index a7ee3c5982..b634028756 100644
--- a/config/initializers/6_rack_profiler.rb
+++ b/config/initializers/6_rack_profiler.rb
@@ -3,4 +3,6 @@ if Rails.env == 'development'
# initialization is skipped so trigger it
Rack::MiniProfilerRails.initialize!(Rails.application)
+ Rack::MiniProfiler.config.position = 'right'
+ Rack::MiniProfiler.config.start_hidden = true
end
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
new file mode 100644
index 0000000000..8f6c567310
--- /dev/null
+++ b/config/initializers/7_omniauth.rb
@@ -0,0 +1,12 @@
+if Gitlab::LDAP::Config.enabled?
+ module OmniAuth::Strategies
+ server = Gitlab.config.ldap.servers.values.first
+ klass = server['provider_class']
+ const_set(klass, Class.new(LDAP)) unless klass == 'LDAP'
+ end
+
+ OmniauthCallbacksController.class_eval do
+ server = Gitlab.config.ldap.servers.values.first
+ alias_method server['provider_name'], :ldap
+ end
+end
diff --git a/config/initializers/acts_as_taggable_on_patch.rb b/config/initializers/acts_as_taggable_on_patch.rb
deleted file mode 100644
index baa77fde39..0000000000
--- a/config/initializers/acts_as_taggable_on_patch.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-# This is a patch to address the issue in https://github.com/mbleigh/acts-as-taggable-on/issues/427 caused by
-# https://github.com/rails/rails/commit/31a43ebc107fbd50e7e62567e5208a05909ec76c
-# gem 'acts-as-taggable-on' has the fix included https://github.com/mbleigh/acts-as-taggable-on/commit/89bbed3864a9252276fb8dd7d535fce280454b90
-# but not in the currently used version of gem ('2.4.1')
-# With replacement of 'acts-as-taggable-on' gem this file will become obsolete
-
-module ActsAsTaggableOn::Taggable
- module Core
- module ClassMethods
- def tagged_with(tags, options = {})
- tag_list = ActsAsTaggableOn::TagList.from(tags)
- empty_result = where("1 = 0")
-
- return empty_result if tag_list.empty?
-
- joins = []
- conditions = []
- having = []
- select_clause = []
-
- context = options.delete(:on)
- owned_by = options.delete(:owned_by)
- alias_base_name = undecorated_table_name.gsub('.','_')
- quote = ActsAsTaggableOn::Tag.using_postgresql? ? '"' : ''
-
- if options.delete(:exclude)
- if options.delete(:wild)
- tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ? ESCAPE '!'", "%#{escape_like(t)}%"]) }.join(" OR ")
- else
- tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ")
- end
-
- conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)})"
-
- if owned_by
- joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" +
- " ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
- " AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" +
- " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{owned_by.id}" +
- " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
- end
-
- elsif options.delete(:any)
- # get tags, drop out if nothing returned (we need at least one)
- tags = if options.delete(:wild)
- ActsAsTaggableOn::Tag.named_like_any(tag_list)
- else
- ActsAsTaggableOn::Tag.named_any(tag_list)
- end
-
- return empty_result unless tags.length > 0
-
- # setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123
- # avoid ambiguous column name
- taggings_context = context ? "_#{context}" : ''
-
- taggings_alias = adjust_taggings_alias(
- "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{sha_prefix(tags.map(&:name).join('_'))}"
- )
-
- tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
- " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
- tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
-
- # don't need to sanitize sql, map all ids and join with OR logic
- conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{t.id}" }.join(" OR ")
- select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one?
-
- if owned_by
- tagging_join << " AND " +
- sanitize_sql([
- "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
- owned_by.id,
- owned_by.class.base_class.to_s
- ])
- end
-
- joins << tagging_join
- else
- tags = ActsAsTaggableOn::Tag.named_any(tag_list)
-
- return empty_result unless tags.length == tag_list.length
-
- tags.each do |tag|
- taggings_alias = adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{sha_prefix(tag.name)}")
- tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
- " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" +
- " AND #{taggings_alias}.tag_id = #{tag.id}"
-
- tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
-
- if owned_by
- tagging_join << " AND " +
- sanitize_sql([
- "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
- owned_by.id,
- owned_by.class.base_class.to_s
- ])
- end
-
- joins << tagging_join
- end
- end
-
- taggings_alias, tags_alias = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group"
-
- if options.delete(:match_all)
- joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
- " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
-
-
- group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
- group = group_columns
- having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
- end
-
- select(select_clause) \
- .joins(joins.join(" ")) \
- .where(conditions.join(" AND ")) \
- .group(group) \
- .having(having) \
- .order(options[:order]) \
- .readonly(false)
- end
- end
- end
-end
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
index d0065b63e5..bfb8656df5 100644
--- a/config/initializers/carrierwave.rb
+++ b/config/initializers/carrierwave.rb
@@ -12,22 +12,30 @@ if File.exists?(aws_file)
aws_secret_access_key: AWS_CONFIG['secret_access_key'], # required
region: AWS_CONFIG['region'], # optional, defaults to 'us-east-1'
}
- config.fog_directory = AWS_CONFIG['bucket'] # required
- config.fog_public = false # optional, defaults to true
- config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
- config.fog_authenticated_url_expiration = 1 << 29 # optional time (in seconds) that authenticated urls will be valid.
- # when fog_public is false and provider is AWS or Google, defaults to 600
+
+ # required
+ config.fog_directory = AWS_CONFIG['bucket']
+
+ # optional, defaults to true
+ config.fog_public = false
+
+ # optional, defaults to {}
+ config.fog_attributes = { 'Cache-Control'=>'max-age=315576000' }
+
+ # optional time (in seconds) that authenticated urls will be valid.
+ # when fog_public is false and provider is AWS or Google, defaults to 600
+ config.fog_authenticated_url_expiration = 1 << 29
end
# Mocking Fog requests, based on: https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Test-Fog-based-uploaders
if Rails.env.test?
Fog.mock!
connection = ::Fog::Storage.new(
- :aws_access_key_id => AWS_CONFIG['access_key_id'],
- :aws_secret_access_key => AWS_CONFIG['secret_access_key'],
- :provider => 'AWS',
- :region => AWS_CONFIG['region']
+ aws_access_key_id: AWS_CONFIG['access_key_id'],
+ aws_secret_access_key: AWS_CONFIG['secret_access_key'],
+ provider: 'AWS',
+ region: AWS_CONFIG['region']
)
- connection.directories.create(:key => AWS_CONFIG['bucket'])
+ connection.directories.create(key: AWS_CONFIG['bucket'])
end
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 34f4f38698..9dce495106 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -145,7 +145,8 @@ Devise.setup do |config|
# Time interval you can reset your password with a reset password key.
# Don't put a too small interval or your users won't have the time to
# change their passwords.
- config.reset_password_within = 2.hours
+ # When someone else invites you to GitLab this time is also used so it should be pretty long.
+ config.reset_password_within = 2.days
# ==> Configuration for :encryptable
# Allow you to use another encryption algorithm besides bcrypt (default). You can use
@@ -204,22 +205,24 @@ Devise.setup do |config|
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
- if Gitlab.config.ldap.enabled
- if Gitlab.config.ldap.allow_username_or_email_login
- email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
- else
- email_stripping_proc = ->(name) {name}
- end
+ if Gitlab::LDAP::Config.enabled?
+ Gitlab.config.ldap.servers.values.each do |server|
+ if server['allow_username_or_email_login']
+ email_stripping_proc = ->(name) {name.gsub(/@.*\z/,'')}
+ else
+ email_stripping_proc = ->(name) {name}
+ end
- config.omniauth :ldap,
- host: Gitlab.config.ldap['host'],
- base: Gitlab.config.ldap['base'],
- uid: Gitlab.config.ldap['uid'],
- port: Gitlab.config.ldap['port'],
- method: Gitlab.config.ldap['method'],
- bind_dn: Gitlab.config.ldap['bind_dn'],
- password: Gitlab.config.ldap['password'],
- name_proc: email_stripping_proc
+ config.omniauth server['provider_name'],
+ host: server['host'],
+ base: server['base'],
+ uid: server['uid'],
+ port: server['port'],
+ method: server['method'],
+ bind_dn: server['bind_dn'],
+ password: server['password'],
+ name_proc: email_stripping_proc
+ end
end
Gitlab.config.omniauth.providers.each do |provider|
diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb
new file mode 100644
index 0000000000..c76a6b8b19
--- /dev/null
+++ b/config/initializers/disable_email_interceptor.rb
@@ -0,0 +1,2 @@
+# Interceptor in lib/disable_email_interceptor.rb
+ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
new file mode 100644
index 0000000000..d422acb31d
--- /dev/null
+++ b/config/initializers/doorkeeper.rb
@@ -0,0 +1,102 @@
+Doorkeeper.configure do
+ # Change the ORM that doorkeeper will use.
+ # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
+ orm :active_record
+
+ # This block will be called to check whether the resource owner is authenticated or not.
+ resource_owner_authenticator do
+ # Put your resource owner authentication logic here.
+ # Example implementation:
+ current_user || redirect_to(new_user_session_url)
+ end
+
+ resource_owner_from_credentials do |routes|
+ u = User.find_by(email: params[:username]) || User.find_by(username: params[:username])
+ u if u && u.valid_password?(params[:password])
+ end
+
+ # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
+ # admin_authenticator do
+ # # Put your admin authentication logic here.
+ # # Example implementation:
+ # Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url)
+ # end
+
+ # Authorization Code expiration time (default 10 minutes).
+ # authorization_code_expires_in 10.minutes
+
+ # Access token expiration time (default 2 hours).
+ # If you want to disable expiration, set this to nil.
+ access_token_expires_in nil
+
+ # Reuse access token for the same resource owner within an application (disabled by default)
+ # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
+ # reuse_access_token
+
+ # Issue access tokens with refresh token (disabled by default)
+ use_refresh_token
+
+ # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled
+ # by default in non-development environments). OAuth2 delegates security in
+ # communication to the HTTPS protocol so it is wise to keep this enabled.
+ #
+ force_ssl_in_redirect_uri false
+
+ # Provide support for an owner to be assigned to each registered application (disabled by default)
+ # Optional parameter confirmation: true (default false) if you want to enforce ownership of
+ # a registered application
+ # Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
+ enable_application_owner confirmation: false
+
+ # Define access token scopes for your provider
+ # For more information go to
+ # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
+ default_scopes :api
+ #optional_scopes :write, :update
+
+ # Change the way client credentials are retrieved from the request object.
+ # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
+ # falls back to the `:client_id` and `:client_secret` params from the `params` object.
+ # Check out the wiki for more information on customization
+ # client_credentials :from_basic, :from_params
+
+ # Change the way access token is authenticated from the request object.
+ # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
+ # falls back to the `:access_token` or `:bearer_token` params from the `params` object.
+ # Check out the wiki for more information on customization
+ access_token_methods :from_access_token_param, :from_bearer_authorization, :from_bearer_param
+
+ # Change the native redirect uri for client apps
+ # When clients register with the following redirect uri, they won't be redirected to any server and the authorization code will be displayed within the provider
+ # The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
+ # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
+ #
+ native_redirect_uri nil#'urn:ietf:wg:oauth:2.0:oob'
+
+ # Specify what grant flows are enabled in array of Strings. The valid
+ # strings and the flows they enable are:
+ #
+ # "authorization_code" => Authorization Code Grant Flow
+ # "implicit" => Implicit Grant Flow
+ # "password" => Resource Owner Password Credentials Grant Flow
+ # "client_credentials" => Client Credentials Grant Flow
+ #
+ # If not specified, Doorkeeper enables all the four grant flows.
+ #
+ grant_flows %w(authorization_code password client_credentials)
+
+ # Under some circumstances you might want to have applications auto-approved,
+ # so that the user skips the authorization step.
+ # For example if dealing with trusted a application.
+ # skip_authorization do |resource_owner, client|
+ # client.superapp? or resource_owner.admin?
+ # end
+
+ # WWW-Authenticate Realm (default "Doorkeeper").
+ # realm "Doorkeeper"
+
+ # Allow dynamic query parameters (disabled by default)
+ # Some applications require dynamic query parameters on their request_uri
+ # set to true if you want this to be allowed
+ # wildcard_redirect_uri false
+end
diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb
new file mode 100644
index 0000000000..e7c9f0ba7c
--- /dev/null
+++ b/config/initializers/gitlab_shell_secret_token.rb
@@ -0,0 +1,19 @@
+# Be sure to restart your server when you modify this file.
+
+require 'securerandom'
+
+# Your secret key for verifying the gitlab_shell.
+
+
+secret_file = Rails.root.join('.gitlab_shell_secret')
+gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret')
+
+unless File.exist? 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)
+end
+
+if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink)
+ FileUtils.symlink(secret_file, gitlab_shell_symlink)
+end
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index 8f8bef42be..ca58ae92d1 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -6,3 +6,5 @@
Mime::Type.register_alias "text/plain", :diff
Mime::Type.register_alias "text/plain", :patch
+Mime::Type.register_alias 'text/html', :markdown
+Mime::Type.register_alias 'text/html', :md
diff --git a/config/initializers/public_key.rb b/config/initializers/public_key.rb
new file mode 100644
index 0000000000..e4f09a2d02
--- /dev/null
+++ b/config/initializers/public_key.rb
@@ -0,0 +1,2 @@
+path = File.expand_path("~/.ssh/bitbucket_rsa.pub")
+Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path)
diff --git a/config/initializers/rack_attack_git_basic_auth.rb b/config/initializers/rack_attack_git_basic_auth.rb
new file mode 100644
index 0000000000..bbbfed6832
--- /dev/null
+++ b/config/initializers/rack_attack_git_basic_auth.rb
@@ -0,0 +1,12 @@
+unless Rails.env.test?
+ # Tell the Rack::Attack Rack middleware to maintain an IP blacklist. We will
+ # update the blacklist from Grack::Auth#authenticate_user.
+ Rack::Attack.blacklist('Git HTTP Basic Auth') do |req|
+ Rack::Attack::Allow2Ban.filter(req.ip, Gitlab.config.rack_attack.git_basic_auth) do
+ # This block only gets run if the IP was not already banned.
+ # Return false, meaning that we do not see anything wrong with the
+ # request at this time
+ false
+ end
+ end
+end
diff --git a/config/initializers/redis-store-fix-expiry.rb b/config/initializers/redis-store-fix-expiry.rb
new file mode 100644
index 0000000000..fce0a13533
--- /dev/null
+++ b/config/initializers/redis-store-fix-expiry.rb
@@ -0,0 +1,44 @@
+# Monkey-patch Redis::Store to make 'setex' and 'expire' work with namespacing
+
+module Gitlab
+ class Redis
+ class Store
+ module Namespace
+ # Redis::Store#setex in redis-store 1.1.4 does not respect namespaces;
+ # this new method does.
+ def setex(key, expires_in, value, options=nil)
+ namespace(key) { |key| super(key, expires_in, value) }
+ end
+
+ # Redis::Store#expire in redis-store 1.1.4 does not respect namespaces;
+ # this new method does.
+ def expire(key, expires_in)
+ namespace(key) { |key| super(key, expires_in) }
+ end
+
+ private
+
+ # Our new definitions of #setex and #expire above assume that the
+ # #namespace method exists. Because we cannot be sure of that, we
+ # re-implement the #namespace method from Redis::Store::Namespace so
+ # that it is available for all Redis::Store instances, whether they use
+ # namespacing or not.
+ #
+ # Based on lib/redis/store/namespace.rb L49-51 (redis-store 1.1.4)
+ def namespace(key)
+ if @namespace
+ yield interpolate(key)
+ else
+ # This Redis::Store instance does not use a namespace so we should
+ # just pass through the key.
+ yield key
+ end
+ end
+ end
+ end
+ end
+end
+
+Redis::Store.class_eval do
+ include Gitlab::Redis::Store::Namespace
+end
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 5fe5270236..b2d59f1c4b 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -2,9 +2,10 @@
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
- servers: Gitlab::Application.config.cache_store[1], # re-use the Redis config from the Rails cache store
+ servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
key: '_gitlab_session',
secure: Gitlab.config.gitlab.https,
httponly: true,
+ expire_after: 1.week,
path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root
)
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index 3711b03796..f0fe2fdfa4 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -1,8 +1,11 @@
-# To enable smtp email delivery for your GitLab instance do next:
+# To enable smtp email delivery for your GitLab instance do the following:
# 1. Rename this file to smtp_settings.rb
# 2. Edit settings inside this file
# 3. Restart GitLab instance
#
+# For full list of options and their values see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+#
+
if Rails.env.production?
Gitlab::Application.config.action_mailer.delivery_method = :smtp
@@ -13,6 +16,7 @@ if Rails.env.production?
password: "123456",
domain: "gitlab.company.com",
authentication: :login,
- enable_starttls_auto: true
+ enable_starttls_auto: true,
+ openssl_verify_mode: 'peer' # See ActionMailer documentation for other possible options
}
end
diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb
new file mode 100644
index 0000000000..d9042c652b
--- /dev/null
+++ b/config/initializers/static_files.rb
@@ -0,0 +1,15 @@
+app = Rails.application
+
+if app.config.serve_static_assets
+ # The `ActionDispatch::Static` middleware intercepts requests for static files
+ # by checking if they exist in the `/public` directory.
+ # We're replacing it with our `Gitlab::Middleware::Static` that does the same,
+ # except ignoring `/uploads`, letting those go through to the GitLab Rails app.
+
+ app.config.middleware.swap(
+ ActionDispatch::Static,
+ Gitlab::Middleware::Static,
+ app.paths["public"].first,
+ app.config.static_cache_control
+ )
+end
diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb
new file mode 100644
index 0000000000..ee246e67d6
--- /dev/null
+++ b/config/initializers/time_zone.rb
@@ -0,0 +1 @@
+Time.zone = Gitlab.config.gitlab.time_zone || Time.zone
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 1cbcde5b3d..f3db5b7476 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -23,8 +23,8 @@ en:
timeout: 'Your session expired, please sign in again to continue.'
inactive: 'Your account was not activated yet.'
sessions:
- signed_in: 'Signed in successfully.'
- signed_out: 'Signed out successfully.'
+ signed_in: ''
+ signed_out: ''
users_sessions:
user:
signed_in: 'Signed in successfully.'
@@ -57,4 +57,4 @@ en:
reset_password_instructions:
subject: 'Reset password instructions'
unlock_instructions:
- subject: 'Unlock Instructions'
+ subject: 'Unlock Instructions'
\ No newline at end of file
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
new file mode 100644
index 0000000000..c5b6b75e7f
--- /dev/null
+++ b/config/locales/doorkeeper.en.yml
@@ -0,0 +1,73 @@
+en:
+ activerecord:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ mongoid:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ mongo_mapper:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ doorkeeper:
+ errors:
+ messages:
+ # Common error messages
+ invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
+ invalid_redirect_uri: 'The redirect uri included is not valid.'
+ unauthorized_client: 'The client is not authorized to perform this request using this method.'
+ access_denied: 'The resource owner or authorization server denied the request.'
+ invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
+ server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
+ temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
+
+ #configuration error messages
+ credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
+ resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.'
+
+ # Access grant errors
+ unsupported_response_type: 'The authorization server does not support this response type.'
+
+ # Access token errors
+ invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
+ invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
+
+ # Password Access token errors
+ invalid_resource_owner: 'The provided resource owner credentials are not valid, or resource owner cannot be found'
+
+ invalid_token:
+ revoked: "The access token was revoked"
+ expired: "The access token expired"
+ unknown: "The access token is invalid"
+ scopes:
+ api: Access your API
+
+ flash:
+ applications:
+ create:
+ notice: 'Application created.'
+ destroy:
+ notice: 'Application deleted.'
+ update:
+ notice: 'Application updated.'
+ authorized_applications:
+ destroy:
+ notice: 'Application revoked.'
diff --git a/config/newrelic.yml b/config/newrelic.yml
new file mode 100644
index 0000000000..9ef922a38d
--- /dev/null
+++ b/config/newrelic.yml
@@ -0,0 +1,16 @@
+# New Relic configuration file
+#
+# This file is here to make sure the New Relic gem stays
+# quiet by default.
+#
+# To enable and configure New Relic, please use
+# environment variables, e.g. NEW_RELIC_ENABLED=true
+
+production:
+ enabled: false
+
+development:
+ enabled: false
+
+test:
+ enabled: false
diff --git a/config/resque.yml.example b/config/resque.yml.example
index 3c7ad0e577..347f3599b2 100644
--- a/config/resque.yml.example
+++ b/config/resque.yml.example
@@ -1,3 +1,3 @@
development: redis://localhost:6379
test: redis://localhost:6379
-production: redis://redis.example.com:6379
+production: unix:/var/run/redis/redis.sock
diff --git a/config/routes.rb b/config/routes.rb
index ce66ea9995..744a99fede 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,11 +2,20 @@ require 'sidekiq/web'
require 'api/api'
Gitlab::Application.routes.draw do
- #
+ use_doorkeeper do
+ controllers applications: 'oauth/applications',
+ authorized_applications: 'oauth/authorized_applications',
+ authorizations: 'oauth/authorizations'
+ end
+
+ # Autocomplete
+ get '/autocomplete/users' => 'autocomplete#users'
+ get '/autocomplete/users/:id' => 'autocomplete#user'
+
+
# Search
- #
- get 'search' => "search#show"
- get 'search/autocomplete' => "search#autocomplete", as: :search_autocomplete
+ get 'search' => 'search#show'
+ get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
# API
API::API.logger Rails.logger
@@ -15,9 +24,9 @@ Gitlab::Application.routes.draw do
# Get all keys of user
get ':username.keys' => 'profiles/keys#get_keys' , constraints: { username: /.*/ }
- constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? }
+ constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? }
constraints constraint do
- mount Sidekiq::Web, at: "/admin/sidekiq", as: :sidekiq
+ mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
end
# Enable Grack support
@@ -28,26 +37,94 @@ Gitlab::Application.routes.draw do
receive_pack: Gitlab.config.gitlab_shell.receive_pack
}), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post]
- #
# Help
- #
-
get 'help' => 'help#index'
- get 'help/:category/:file' => 'help#show', as: :help_page
+ get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ }
get 'help/shortcuts'
+ get 'help/ui' => 'help#ui'
#
# Global snippets
#
resources :snippets do
member do
- get "raw"
+ get 'raw'
end
end
- get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ }
+ get '/s/:username' => 'snippets#user_index', as: :user_snippets, constraints: { username: /.*/ }
#
- # Explroe area
+ # Invites
+ #
+
+ resources :invites, only: [:show], constraints: { id: /[A-Za-z0-9_-]+/ } do
+ member do
+ post :accept
+ match :decline, via: [:get, :post]
+ end
+ end
+
+ #
+ # Import
+ #
+ namespace :import do
+ resource :github, only: [:create, :new], controller: :github do
+ get :status
+ get :callback
+ get :jobs
+ end
+
+ resource :gitlab, only: [:create, :new], controller: :gitlab do
+ get :status
+ get :callback
+ get :jobs
+ end
+
+ resource :bitbucket, only: [:create, :new], controller: :bitbucket do
+ get :status
+ get :callback
+ get :jobs
+ end
+
+ resource :gitorious, only: [:create, :new], controller: :gitorious do
+ get :status
+ get :callback
+ get :jobs
+ end
+
+ resource :google_code, only: [:create, :new], controller: :google_code do
+ get :status
+ post :callback
+ get :jobs
+
+ get :new_user_map, path: :user_map
+ post :create_user_map, path: :user_map
+ end
+ end
+
+ #
+ # Uploads
+ #
+
+ scope path: :uploads do
+ # Note attachments and User/Group/Project avatars
+ get ":model/:mounted_as/:id/:filename",
+ to: "uploads#show",
+ constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ }
+
+ # Project markdown uploads
+ get ":namespace_id/:project_id/:secret/:filename",
+ to: "projects/uploads#show",
+ constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ }
+ end
+
+ # Redirect old note attachments path to new uploads path.
+ get "files/note/:id/:filename",
+ to: redirect("uploads/note/attachment/%{id}/%{filename}"),
+ constraints: { filename: /[^\/]+/ }
+
+ #
+ # Explore area
#
namespace :explore do
resources :projects, only: [:index] do
@@ -58,23 +135,19 @@ Gitlab::Application.routes.draw do
end
resources :groups, only: [:index]
- root to: "projects#trending"
+ root to: 'projects#trending'
end
# Compatibility with old routing
- get 'public' => "explore/projects#index"
- get 'public/projects' => "explore/projects#index"
-
- #
- # Attachments serving
- #
- get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ }
+ get 'public' => 'explore/projects#index'
+ get 'public/projects' => 'explore/projects#index'
#
# Admin Area
#
namespace :admin do
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
+ resources :keys, only: [:show, :destroy]
member do
put :team_update
put :block
@@ -83,12 +156,16 @@ Gitlab::Application.routes.draw do
end
end
+ resources :applications
+
resources :groups, constraints: { id: /[^\/]+/ } do
member do
- put :project_teams_update
+ put :members_update
end
end
+ resources :deploy_keys, only: [:index, :show, :new, :create, :destroy]
+
resources :hooks, only: [:index, :create, :destroy] do
get :test
end
@@ -97,13 +174,26 @@ Gitlab::Application.routes.draw do
resource :logs, only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
- resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do
- member do
- put :transfer
+ resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
+ root to: 'projects#index', as: :projects
+
+ resources(:projects,
+ path: '/',
+ constraints: { id: /[a-zA-Z.0-9_\-]+/ },
+ only: [:index, :show]) do
+ root to: 'projects#show'
+
+ member do
+ put :transfer
+ end
end
end
- root to: "dashboard#index"
+ resource :application_settings, only: [:show, :update] do
+ resources :services
+ end
+
+ root to: 'dashboard#index'
end
#
@@ -113,13 +203,18 @@ Gitlab::Application.routes.draw do
member do
get :history
get :design
+ get :applications
put :reset_private_token
put :update_username
end
scope module: :profiles do
- resource :account, only: [:show, :update]
+ resource :account, only: [:show, :update] do
+ member do
+ delete :unlink
+ end
+ end
resource :notifications, only: [:show, :update]
resource :password, only: [:new, :create, :edit, :update] do
member do
@@ -128,213 +223,301 @@ Gitlab::Application.routes.draw do
end
resources :keys
resources :emails, only: [:index, :create, :destroy]
- resources :groups, only: [:index] do
- member do
- delete :leave
- end
- end
resource :avatar, only: [:destroy]
end
end
- match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get
+ get 'u/:username/calendar' => 'users#calendar', as: :user_calendar,
+ constraints: { username: /.*/ }
+
+ get 'u/:username/calendar_activities' => 'users#calendar_activities', as: :user_calendar_activities,
+ constraints: { username: /.*/ }
+
+ get '/u/:username' => 'users#show', as: :user,
+ constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
#
# Dashboard Area
#
- resource :dashboard, controller: "dashboard", only: [:show] do
+ resource :dashboard, controller: 'dashboard', only: [:show] do
member do
- get :projects
get :issues
get :merge_requests
end
+
+ scope module: :dashboard do
+ resources :milestones, only: [:index, :show]
+
+ resources :groups, only: [:index]
+
+ resources :projects, only: [] do
+ collection do
+ get :starred
+ end
+ end
+ end
end
#
# Groups Area
#
- resources :groups, constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} do
+ resources :groups, constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } do
member do
get :issues
get :merge_requests
- get :members
get :projects
end
- resources :users_groups, only: [:create, :update, :destroy]
-
scope module: :groups do
+ resources :group_members, only: [:index, :create, :update, :destroy] do
+ post :resend_invite, on: :member
+ delete :leave, on: :collection
+ end
+
resource :avatar, only: [:destroy]
- resources :milestones
+ resources :milestones, only: [:index, :show, :update]
end
end
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create]
- devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions }
+ devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions, confirmations: :confirmations }
devise_scope :user do
- get "/users/auth/:provider/omniauth_error" => "omniauth_callbacks#omniauth_error", as: :omniauth_error
+ get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
end
+
+ root to: "dashboard#show"
+
#
# Project Area
#
- resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
- member do
- put :transfer
- post :fork
- post :archive
- post :unarchive
- post :upload_image
- post :toggle_star
- get :autocomplete_sources
- get :import
- put :retry_import
- end
-
- scope module: :projects do
- resources :blob, only: [:show, :destroy], constraints: { id: /.+/ } do
- get :diff, on: :member
+ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
+ resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+/ }, except:
+ [:new, :create, :index], path: "/") do
+ member do
+ put :transfer
+ post :archive
+ post :unarchive
+ post :toggle_star
+ post :markdown_preview
+ get :autocomplete_sources
end
- resources :raw, only: [:show], constraints: {id: /.+/}
- resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
- resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do
- post :preview, on: :member
- end
- resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new'
- resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
- resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
- resources :compare, only: [:index, :create]
- resources :blame, only: [:show], constraints: {id: /.+/}
- resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
- resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
- match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
+ scope module: :projects do
+ # Blob routes:
+ get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob'
+ post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob'
+ get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob'
+ put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
+ post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
- resources :snippets, constraints: {id: /\d+/} do
+ scope do
+ get(
+ '/blob/*id/diff',
+ to: 'blob#diff',
+ constraints: { id: /.+/, format: false },
+ as: :blob_diff
+ )
+ get(
+ '/blob/*id',
+ to: 'blob#show',
+ constraints: { id: /.+/, format: false },
+ as: :blob
+ )
+ delete(
+ '/blob/*id',
+ to: 'blob#destroy',
+ constraints: { id: /.+/, format: false }
+ )
+ end
+
+ scope do
+ get(
+ '/raw/*id',
+ to: 'raw#show',
+ constraints: { id: /.+/, format: /(html|js)/ },
+ as: :raw
+ )
+ end
+
+ scope do
+ get(
+ '/tree/*id',
+ to: 'tree#show',
+ constraints: { id: /.+/, format: /(html|js)/ },
+ as: :tree
+ )
+ end
+
+ scope do
+ get(
+ '/blame/*id',
+ to: 'blame#show',
+ constraints: { id: /.+/, format: /(html|js)/ },
+ as: :blame
+ )
+ end
+
+ scope do
+ get(
+ '/commits/*id',
+ to: 'commits#show',
+ constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ },
+ as: :commits
+ )
+ end
+
+ resource :avatar, only: [:show, :destroy]
+ resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do
+ get :branches, on: :member
+ end
+
+ resources :compare, only: [:index, :create]
+ resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }
+
+ resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do
member do
- get "raw"
+ get :commits
end
end
- resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do
- collection do
- get :pages
- put ':id' => 'wikis#update'
- get :git_access
+ get '/compare/:from...:to' => 'compare#show', :as => 'compare',
+ :constraints => { from: /.+/, to: /.+/ }
+
+ resources :snippets, constraints: { id: /\d+/ } do
+ member do
+ get 'raw'
+ end
end
- member do
- get "history"
- end
- end
+ resources :wikis, only: [:show, :edit, :destroy, :create], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ } do
+ collection do
+ get :pages
+ put ':id' => 'wikis#update'
+ get :git_access
+ end
- resource :repository, only: [:show] do
- member do
- get "stats"
- get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex }
- end
- end
-
- resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
- member do
- get :test
- end
- end
-
- resources :deploy_keys, constraints: {id: /\d+/} do
- member do
- put :enable
- put :disable
- end
- end
-
- resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
- resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
- resources :protected_branches, only: [:index, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
-
- resources :refs, only: [] do
- collection do
- get "switch"
+ member do
+ get 'history'
+ end
end
- member do
- # tree viewer logs
- get "logs_tree", constraints: { id: Gitlab::Regex.git_reference_regex }
- get "logs_tree/:path" => "refs#logs_tree",
- as: :logs_file,
- constraints: {
- id: Gitlab::Regex.git_reference_regex,
+ resource :repository, only: [:show, :create] do
+ member do
+ get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
+ end
+ end
+
+ resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
+ member do
+ get :test
+ end
+ end
+
+ resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :show, :new, :create] do
+ member do
+ put :enable
+ put :disable
+ end
+ end
+
+ resource :fork, only: [:new, :create]
+ resource :import, only: [:new, :create, :show]
+
+ resources :refs, only: [] do
+ collection do
+ get 'switch'
+ end
+
+ member do
+ # tree viewer logs
+ get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
+ get 'logs_tree/:path' => 'refs#logs_tree', as: :logs_file, constraints: {
+ id: Gitlab::Regex.git_reference_regex,
path: /.*/
}
+ end
+ end
+
+ resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do
+ member do
+ get :diffs
+ post :automerge
+ get :automerge_check
+ get :ci_status
+ post :toggle_subscription
+ end
+
+ collection do
+ get :branch_from
+ get :branch_to
+ get :update_branches
+ end
+ end
+
+ resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+ resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+ resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+
+ resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
+ member do
+ get :test
+ end
+ end
+
+ resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do
+ member do
+ put :sort_issues
+ put :sort_merge_requests
+ end
+ end
+
+ resources :labels, constraints: { id: /\d+/ } do
+ collection do
+ post :generate
+ end
+ end
+
+ resources :issues, constraints: { id: /\d+/ }, except: [:destroy] do
+ member do
+ post :toggle_subscription
+ end
+ collection do
+ post :bulk_update
+ end
+ end
+
+ resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do
+ collection do
+ delete :leave
+
+ # Used for import team
+ # from another project
+ get :import
+ post :apply_import
+ end
+
+ member do
+ post :resend_invite
+ end
+ end
+
+ resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
+ member do
+ delete :delete_attachment
+ end
+ end
+
+ resources :uploads, only: [:create] do
+ collection do
+ get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ }
+ end
end
end
- resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do
- member do
- get :diffs
- post :automerge
- get :automerge_check
- get :ci_status
- end
-
- collection do
- get :branch_from
- get :branch_to
- get :update_branches
- end
- end
-
- resources :hooks, only: [:index, :create, :destroy], constraints: {id: /\d+/} do
- member do
- get :test
- end
- end
-
- resources :team, controller: 'team_members', only: [:index]
- resources :milestones, except: [:destroy], constraints: {id: /\d+/} do
- member do
- put :sort_issues
- put :sort_merge_requests
- end
- end
-
- resources :labels, constraints: {id: /\d+/} do
- collection do
- post :generate
- end
- end
-
- resources :issues, constraints: {id: /\d+/}, except: [:destroy] do
- collection do
- post :bulk_update
- end
- end
-
- resources :team_members, except: [:index, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do
- collection do
- delete :leave
-
- # Used for import team
- # from another project
- get :import
- post :apply_import
- end
- end
-
- resources :notes, only: [:index, :create, :destroy, :update], constraints: {id: /\d+/} do
- member do
- delete :delete_attachment
- end
-
- collection do
- post :preview
- end
- end
end
end
- get ':id' => "namespaces#show", constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
-
- root to: "dashboard#show"
+ get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index e88a452233..86a5512e76 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -13,9 +13,10 @@
#
# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
-# Use at least one worker per core if you're on a dedicated server,
-# more will usually help for _short_ waits on databases/caches.
-worker_processes 2
+# Read about unicorn workers here:
+# http://doc.gitlab.com/ee/install/requirements.html#unicorn-workers
+#
+worker_processes 3
# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
@@ -28,17 +29,18 @@ worker_processes 2
# "current" directory that Capistrano sets up.
working_directory "/home/git/gitlab" # available in 0.94.0+
-# listen on both a Unix domain socket and a TCP port,
-# we use a shorter backlog for quicker failover when busy
-listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 64
+# Listen on both a Unix domain socket and a TCP port.
+# If you are load-balancing multiple Unicorn masters, lower the backlog
+# setting to e.g. 64 for faster failover.
+listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 1024
listen "127.0.0.1:8080", :tcp_nopush => true
# nuke workers after 30 seconds instead of 60 seconds (the default)
#
-# NOTICE: git push over http depends on this value.
-# If you want be able to push huge amount of data to git repository over http
-# you will have to increase this value too.
-#
+# NOTICE: git push over http depends on this value.
+# If you want be able to push huge amount of data to git repository over http
+# you will have to increase this value too.
+#
# Example of output if you try to push 1GB repo to GitLab over http.
# -> git push http://gitlab.... master
#
@@ -48,7 +50,7 @@ listen "127.0.0.1:8080", :tcp_nopush => true
#
# For more information see http://stackoverflow.com/a/21682112/752049
#
-timeout 30
+timeout 60
# feel free to point this anywhere accessible on the filesystem
pid "/home/git/gitlab/tmp/pids/unicorn.pid"
diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development
index 94a7061451..3cd00d53a1 100644
--- a/config/unicorn.rb.example.development
+++ b/config/unicorn.rb.example.development
@@ -1,2 +1,2 @@
worker_processes 2
-timeout 30
+timeout 60
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index 1927a39f38..bba2fc4b18 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -1,11 +1,13 @@
-User.seed do |s|
- s.id = 1
- s.name = "Administrator"
- s.email = "admin@example.com"
- s.username = 'root'
- s.password = "5iveL!fe"
- s.password_confirmation = "5iveL!fe"
- s.admin = true
- s.projects_limit = 100
- s.confirmed_at = DateTime.now
+Gitlab::Seeder.quiet do
+ User.seed do |s|
+ s.id = 1
+ s.name = 'Administrator'
+ s.email = 'admin@example.com'
+ s.notification_email = 'admin@example.com'
+ s.username = 'root'
+ s.password = '5iveL!fe'
+ s.admin = true
+ s.projects_limit = 100
+ s.confirmed_at = DateTime.now
+ end
end
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index b93229a060..ae4c0550a4 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -4,10 +4,10 @@ Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
project_urls = [
'https://github.com/documentcloud/underscore.git',
- 'https://github.com/gitlabhq/gitlabhq.git',
- 'https://github.com/gitlabhq/gitlab-ci.git',
- 'https://github.com/gitlabhq/gitlab-shell.git',
- 'https://github.com/gitlabhq/testme.git',
+ 'https://gitlab.com/gitlab-org/gitlab-ce.git',
+ 'https://gitlab.com/gitlab-org/gitlab-ci.git',
+ 'https://gitlab.com/gitlab-org/gitlab-shell.git',
+ 'https://gitlab.com/gitlab-org/gitlab-test.git',
'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git',
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index f4a5b8631a..24952a1f66 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -1,15 +1,31 @@
Gitlab::Seeder.quiet do
(2..20).each do |i|
begin
- User.seed(:id, [{
- id: i,
+ User.create!(
username: Faker::Internet.user_name,
name: Faker::Name.name,
email: Faker::Internet.email,
- confirmed_at: DateTime.now
- }])
+ confirmed_at: DateTime.now,
+ password: '12345678'
+ )
+
print '.'
- rescue ActiveRecord::RecordNotSaved
+ rescue ActiveRecord::RecordInvalid
+ print 'F'
+ end
+ end
+
+ (1..5).each do |i|
+ begin
+ User.create!(
+ username: "user#{i}",
+ name: "User #{i}",
+ email: "user#{i}@example.com",
+ confirmed_at: DateTime.now,
+ password: '12345678'
+ )
+ print '.'
+ rescue ActiveRecord::RecordInvalid
print 'F'
end
end
diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb
index dfbe75fd20..3e8cdcd67b 100644
--- a/db/fixtures/development/06_teams.rb
+++ b/db/fixtures/development/06_teams.rb
@@ -1,7 +1,7 @@
Gitlab::Seeder.quiet do
Group.all.each do |group|
User.all.sample(4).each do |user|
- if group.add_users([user.id], UsersGroup.group_access_roles.values.sample)
+ if group.add_users([user.id], Gitlab::Access.values.sample)
print '.'
else
print 'F'
@@ -11,7 +11,7 @@ Gitlab::Seeder.quiet do
Project.all.each do |project|
User.all.sample(4).each do |user|
- if project.team << [user, UsersProject.access_roles.values.sample]
+ if project.team << [user, Gitlab::Access.values.sample]
print '.'
else
print 'F'
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 03f8de1298..f9b2fd8b05 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -20,4 +20,22 @@ Gitlab::Seeder.quiet do
print '.'
end
end
+
+ project = Project.find_with_namespace('gitlab-org/gitlab-test')
+
+ params = {
+ source_branch: 'feature',
+ target_branch: 'master',
+ title: 'Can be automatically merged'
+ }
+ MergeRequests::CreateService.new(project, User.admins.first, params).execute
+ print '.'
+
+ params = {
+ source_branch: 'feature_conflict',
+ target_branch: 'feature',
+ title: 'Cannot be automatically merged'
+ }
+ MergeRequests::CreateService.new(project, User.admins.first, params).execute
+ print '.'
end
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index ff91e8430a..b3a6f39c7d 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -1,9 +1,26 @@
Gitlab::Seeder.quiet do
- contents = [
- `curl https://gist.githubusercontent.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`,
- `curl https://gist.githubusercontent.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh`,
- `curl https://gist.githubusercontent.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`,
- ]
+ content =< { 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) }
+
+ delegate :name, :username, :email, to: :user, prefix: true
+end
+eos
(1..50).each do |i|
user = User.all.sample
@@ -12,10 +29,11 @@ Gitlab::Seeder.quiet do
id: i,
author_id: user.id,
title: Faker::Lorem.sentence(3),
- file_name: Faker::Internet.domain_word + '.sh',
- private: [true, false].sample,
- content: contents.sample,
+ file_name: Faker::Internet.domain_word + '.rb',
+ visibility_level: Gitlab::VisibilityLevel.values.sample,
+ content: content,
}])
+
print('.')
end
end
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index c00ba3c10b..8b560ee09e 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -1,10 +1,17 @@
+if ENV['GITLAB_ROOT_PASSWORD'].blank?
+ password = '5iveL!fe'
+ expire_time = Time.now
+else
+ password = ENV['GITLAB_ROOT_PASSWORD']
+ expire_time = nil
+end
+
admin = User.create(
email: "admin@example.com",
name: "Administrator",
username: 'root',
- password: "5iveL!fe",
- password_confirmation: "5iveL!fe",
- password_expires_at: Time.now,
+ password: password,
+ password_expires_at: expire_time,
theme_id: Gitlab::Theme::MARS
)
@@ -15,10 +22,10 @@ admin.save!
admin.confirm!
if admin.valid?
-puts %q[
+puts %Q[
Administrator account created:
login.........root
-password......5iveL!fe
+password......#{password}
]
end
diff --git a/db/migrate/20140125162722_add_avatar_to_projects.rb b/db/migrate/20140125162722_add_avatar_to_projects.rb
new file mode 100644
index 0000000000..9523ac722f
--- /dev/null
+++ b/db/migrate/20140125162722_add_avatar_to_projects.rb
@@ -0,0 +1,5 @@
+class AddAvatarToProjects < ActiveRecord::Migration
+ def change
+ add_column :projects, :avatar, :string
+ end
+end
diff --git a/db/migrate/20140903115954_migrate_to_new_shell.rb b/db/migrate/20140903115954_migrate_to_new_shell.rb
new file mode 100644
index 0000000000..2d83210951
--- /dev/null
+++ b/db/migrate/20140903115954_migrate_to_new_shell.rb
@@ -0,0 +1,10 @@
+class MigrateToNewShell < ActiveRecord::Migration
+ def change
+ gitlab_shell_path = Gitlab.config.gitlab_shell.path
+ if system("#{gitlab_shell_path}/bin/create-hooks")
+ puts 'Repositories updated with new hooks'
+ else
+ raise 'Failed to rewrite gitlab-shell hooks in repositories'
+ end
+ end
+end
diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb
new file mode 100644
index 0000000000..d45a10465b
--- /dev/null
+++ b/db/migrate/20140907220153_serialize_service_properties.rb
@@ -0,0 +1,42 @@
+class SerializeServiceProperties < ActiveRecord::Migration
+ def change
+ unless column_exists?(:services, :properties)
+ add_column :services, :properties, :text
+ end
+
+ Service.reset_column_information
+
+ associations =
+ {
+ AssemblaService: [:token, :subdomain],
+ CampfireService: [:token, :subdomain, :room],
+ EmailsOnPushService: [:recipients],
+ FlowdockService: [:token],
+ GemnasiumService: [:api_key, :token],
+ GitlabCiService: [:token, :project_url],
+ HipchatService: [:token, :room],
+ PivotaltrackerService: [:token],
+ SlackService: [:subdomain, :token, :room],
+ JenkinsService: [:project_url],
+ JiraService: [:project_url, :username, :password,
+ :api_version, :jira_issue_transition_id],
+ }
+
+ Service.find_each(batch_size: 500).each do |service|
+ associations[service.type.to_sym].each do |attribute|
+ service.send("#{attribute}=", service.attributes[attribute.to_s])
+ end
+
+ service.save(validate: false)
+ end
+
+ if column_exists?(:services, :project_url)
+ remove_column :services, :project_url, :string
+ remove_column :services, :subdomain, :string
+ remove_column :services, :room, :string
+ remove_column :services, :recipients, :text
+ remove_column :services, :api_key, :string
+ remove_column :services, :token, :string
+ end
+ end
+end
diff --git a/db/migrate/20140914113604_add_members_table.rb b/db/migrate/20140914113604_add_members_table.rb
new file mode 100644
index 0000000000..d311f3033e
--- /dev/null
+++ b/db/migrate/20140914113604_add_members_table.rb
@@ -0,0 +1,19 @@
+class AddMembersTable < ActiveRecord::Migration
+ def change
+ create_table :members do |t|
+ t.integer :access_level, null: false
+ t.integer :source_id, null: false
+ t.string :source_type, null: false
+ t.integer :user_id, null: false
+ t.integer :notification_level, null: false
+ t.string :type
+
+ t.timestamps
+ end
+
+ add_index :members, :type
+ add_index :members, :user_id
+ add_index :members, :access_level
+ add_index :members, [:source_id, :source_type]
+ end
+end
diff --git a/db/migrate/20140914145549_migrate_to_new_members_model.rb b/db/migrate/20140914145549_migrate_to_new_members_model.rb
new file mode 100644
index 0000000000..2a5a49c724
--- /dev/null
+++ b/db/migrate/20140914145549_migrate_to_new_members_model.rb
@@ -0,0 +1,11 @@
+class MigrateToNewMembersModel < ActiveRecord::Migration
+ def up
+ execute "INSERT INTO members ( user_id, source_id, source_type, access_level, notification_level, type ) SELECT user_id, group_id, 'Namespace', group_access, notification_level, 'GroupMember' FROM users_groups"
+ execute "INSERT INTO members ( user_id, source_id, source_type, access_level, notification_level, type ) SELECT user_id, project_id, 'Project', project_access, notification_level, 'ProjectMember' FROM users_projects"
+ end
+
+ def down
+ Member.delete_all
+ end
+end
+
diff --git a/db/migrate/20140914173417_remove_old_member_tables.rb b/db/migrate/20140914173417_remove_old_member_tables.rb
new file mode 100644
index 0000000000..408b9551db
--- /dev/null
+++ b/db/migrate/20140914173417_remove_old_member_tables.rb
@@ -0,0 +1,26 @@
+class RemoveOldMemberTables < ActiveRecord::Migration
+ def up
+ drop_table :users_groups
+ drop_table :users_projects
+ end
+
+ def down
+ create_table :users_groups do |t|
+ t.integer :group_access, null: false
+ t.integer :group_id, null: false
+ t.integer :user_id, null: false
+ t.integer :notification_level, null: false, default: 3
+
+ t.timestamps
+ end
+
+ create_table :users_projects do |t|
+ t.integer :project_access, null: false
+ t.integer :project_id, null: false
+ t.integer :user_id, null: false
+ t.integer :notification_level, null: false, default: 3
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
new file mode 100644
index 0000000000..5836cd6b8d
--- /dev/null
+++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
@@ -0,0 +1,17 @@
+class MoveSlackServiceToWebhook < ActiveRecord::Migration
+ def change
+ SlackService.all.each do |slack_service|
+ if ["token", "subdomain"].all? { |property| slack_service.properties.key? property }
+ token = slack_service.properties['token']
+ subdomain = slack_service.properties['subdomain']
+ webhook = "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}"
+ slack_service.properties['webhook'] = webhook
+ slack_service.properties.delete('token')
+ slack_service.properties.delete('subdomain')
+ # Room is configured on the Slack side
+ slack_service.properties.delete('room')
+ slack_service.save(validate: false)
+ end
+ end
+ end
+end
diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
new file mode 100644
index 0000000000..7f125acb5d
--- /dev/null
+++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
@@ -0,0 +1,21 @@
+class AddVisibilityLevelToSnippet < ActiveRecord::Migration
+ def up
+ add_column :snippets, :visibility_level, :integer, :default => 0, :null => false
+
+ Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+
+ add_index :snippets, :visibility_level
+
+ remove_column :snippets, :private
+ end
+
+ def down
+ add_column :snippets, :private, :boolean, :default => false, :null => false
+
+ Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false)
+ Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true)
+
+ remove_column :snippets, :visibility_level
+ end
+end
diff --git a/db/migrate/20141121133009_add_timestamps_to_members.rb b/db/migrate/20141121133009_add_timestamps_to_members.rb
new file mode 100644
index 0000000000..ef6d4dedf3
--- /dev/null
+++ b/db/migrate/20141121133009_add_timestamps_to_members.rb
@@ -0,0 +1,15 @@
+# In 20140914145549_migrate_to_new_members_model.rb we forgot to set the
+# created_at and updated_at times for new records in the 'members' table. This
+# became a problem after commit c8e78d972a5a628870eefca0f2ccea0199c55bda which
+# was added in GitLab 7.5. With this migration we ensure that all rows in
+# 'members' have at least some created_at and updated_at timestamp.
+class AddTimestampsToMembers < ActiveRecord::Migration
+ def up
+ execute "UPDATE members SET created_at = NOW() WHERE created_at is NULL"
+ execute "UPDATE members SET updated_at = NOW() WHERE updated_at is NULL"
+ end
+
+ def down
+ # no change
+ end
+end
diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb
new file mode 100644
index 0000000000..a85b0426ce
--- /dev/null
+++ b/db/migrate/20141121161704_add_identity_table.rb
@@ -0,0 +1,46 @@
+class AddIdentityTable < ActiveRecord::Migration
+ def up
+ create_table :identities do |t|
+ t.string :extern_uid
+ t.string :provider
+ t.references :user
+ end
+
+ add_index :identities, :user_id
+
+ execute < 2
+ end
+end
diff --git a/db/migrate/20150205211843_add_timestamps_to_identities.rb b/db/migrate/20150205211843_add_timestamps_to_identities.rb
new file mode 100644
index 0000000000..77cddbfec3
--- /dev/null
+++ b/db/migrate/20150205211843_add_timestamps_to_identities.rb
@@ -0,0 +1,5 @@
+class AddTimestampsToIdentities < ActiveRecord::Migration
+ def change
+ add_timestamps(:identities)
+ end
+end
diff --git a/db/migrate/20150206181414_add_index_to_created_at.rb b/db/migrate/20150206181414_add_index_to_created_at.rb
new file mode 100644
index 0000000000..fc624fca60
--- /dev/null
+++ b/db/migrate/20150206181414_add_index_to_created_at.rb
@@ -0,0 +1,16 @@
+class AddIndexToCreatedAt < ActiveRecord::Migration
+ def change
+ add_index "users", [:created_at, :id]
+ add_index "members", [:created_at, :id]
+ add_index "projects", [:created_at, :id]
+ add_index "issues", [:created_at, :id]
+ add_index "merge_requests", [:created_at, :id]
+ add_index "milestones", [:created_at, :id]
+ add_index "namespaces", [:created_at, :id]
+ add_index "notes", [:created_at, :id]
+ add_index "identities", [:created_at, :id]
+ add_index "keys", [:created_at, :id]
+ add_index "web_hooks", [:created_at, :id]
+ add_index "snippets", [:created_at, :id]
+ end
+end
diff --git a/db/migrate/20150206222854_add_notification_email_to_user.rb b/db/migrate/20150206222854_add_notification_email_to_user.rb
new file mode 100644
index 0000000000..ab80f7e582
--- /dev/null
+++ b/db/migrate/20150206222854_add_notification_email_to_user.rb
@@ -0,0 +1,11 @@
+class AddNotificationEmailToUser < ActiveRecord::Migration
+ def up
+ add_column :users, :notification_email, :string
+
+ execute "UPDATE users SET notification_email = email"
+ end
+
+ def down
+ remove_column :users, :notification_email
+ end
+end
diff --git a/db/migrate/20150209222013_add_missing_index.rb b/db/migrate/20150209222013_add_missing_index.rb
new file mode 100644
index 0000000000..a816c2e9e8
--- /dev/null
+++ b/db/migrate/20150209222013_add_missing_index.rb
@@ -0,0 +1,5 @@
+class AddMissingIndex < ActiveRecord::Migration
+ def change
+ add_index "services", [:created_at, :id]
+ end
+end
diff --git a/db/migrate/20150211172122_add_template_to_service.rb b/db/migrate/20150211172122_add_template_to_service.rb
new file mode 100644
index 0000000000..b1bfbc45ee
--- /dev/null
+++ b/db/migrate/20150211172122_add_template_to_service.rb
@@ -0,0 +1,5 @@
+class AddTemplateToService < ActiveRecord::Migration
+ def change
+ add_column :services, :template, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20150211174341_allow_null_in_services_project_id.rb b/db/migrate/20150211174341_allow_null_in_services_project_id.rb
new file mode 100644
index 0000000000..68f0281279
--- /dev/null
+++ b/db/migrate/20150211174341_allow_null_in_services_project_id.rb
@@ -0,0 +1,5 @@
+class AllowNullInServicesProjectId < ActiveRecord::Migration
+ def change
+ change_column :services, :project_id, :integer, null: true
+ end
+end
diff --git a/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb b/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb
new file mode 100644
index 0000000000..a043917239
--- /dev/null
+++ b/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddTwitterSharingEnabledToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :twitter_sharing_enabled, :boolean, default: true
+ end
+end
diff --git a/db/migrate/20150213114800_add_hide_no_password_to_user.rb b/db/migrate/20150213114800_add_hide_no_password_to_user.rb
new file mode 100644
index 0000000000..685f084427
--- /dev/null
+++ b/db/migrate/20150213114800_add_hide_no_password_to_user.rb
@@ -0,0 +1,5 @@
+class AddHideNoPasswordToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :hide_no_password, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20150213121042_add_password_automatically_set_to_user.rb b/db/migrate/20150213121042_add_password_automatically_set_to_user.rb
new file mode 100644
index 0000000000..c3c7c1ffc7
--- /dev/null
+++ b/db/migrate/20150213121042_add_password_automatically_set_to_user.rb
@@ -0,0 +1,5 @@
+class AddPasswordAutomaticallySetToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :password_automatically_set, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb
new file mode 100644
index 0000000000..23ac1b399e
--- /dev/null
+++ b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb
@@ -0,0 +1,6 @@
+class AddBitbucketAccessTokenAndSecretToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :bitbucket_access_token, :string
+ add_column :users, :bitbucket_access_token_secret, :string
+ end
+end
diff --git a/db/migrate/20150219004514_add_events_to_services.rb b/db/migrate/20150219004514_add_events_to_services.rb
new file mode 100644
index 0000000000..cf73a0174f
--- /dev/null
+++ b/db/migrate/20150219004514_add_events_to_services.rb
@@ -0,0 +1,8 @@
+class AddEventsToServices < ActiveRecord::Migration
+ def change
+ add_column :services, :push_events, :boolean, :default => true
+ add_column :services, :issues_events, :boolean, :default => true
+ add_column :services, :merge_requests_events, :boolean, :default => true
+ add_column :services, :tag_push_events, :boolean, :default => true
+ end
+end
diff --git a/db/migrate/20150223022001_set_missing_last_activity_at.rb b/db/migrate/20150223022001_set_missing_last_activity_at.rb
new file mode 100644
index 0000000000..3f6d4d8347
--- /dev/null
+++ b/db/migrate/20150223022001_set_missing_last_activity_at.rb
@@ -0,0 +1,8 @@
+class SetMissingLastActivityAt < ActiveRecord::Migration
+ def up
+ execute "UPDATE projects SET last_activity_at = updated_at WHERE last_activity_at IS NULL"
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20150225065047_add_note_events_to_services.rb b/db/migrate/20150225065047_add_note_events_to_services.rb
new file mode 100644
index 0000000000..d54ba9e482
--- /dev/null
+++ b/db/migrate/20150225065047_add_note_events_to_services.rb
@@ -0,0 +1,5 @@
+class AddNoteEventsToServices < ActiveRecord::Migration
+ def change
+ add_column :services, :note_events, :boolean, default: true, null: false
+ end
+end
diff --git a/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb b/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb
new file mode 100644
index 0000000000..494c3033bf
--- /dev/null
+++ b/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddRestrictedVisibilityLevelsToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :restricted_visibility_levels, :text
+ end
+end
diff --git a/db/migrate/20150306023106_fix_namespace_duplication.rb b/db/migrate/20150306023106_fix_namespace_duplication.rb
new file mode 100644
index 0000000000..334e557455
--- /dev/null
+++ b/db/migrate/20150306023106_fix_namespace_duplication.rb
@@ -0,0 +1,21 @@
+class FixNamespaceDuplication < ActiveRecord::Migration
+ def up
+ #fixes path duplication
+ select_all('SELECT MAX(id) max, COUNT(id) cnt, path FROM namespaces GROUP BY path HAVING COUNT(id) > 1').each do |nms|
+ bad_nms_ids = select_all("SELECT id FROM namespaces WHERE path = '#{nms['path']}' AND id <> #{nms['max']}").map{|x| x["id"]}
+ execute("UPDATE projects SET namespace_id = #{nms["max"]} WHERE namespace_id IN(#{bad_nms_ids.join(', ')})")
+ execute("DELETE FROM namespaces WHERE id IN(#{bad_nms_ids.join(', ')})")
+ end
+
+ #fixes name duplication
+ select_all('SELECT MAX(id) max, COUNT(id) cnt, name FROM namespaces GROUP BY name HAVING COUNT(id) > 1').each do |nms|
+ bad_nms_ids = select_all("SELECT id FROM namespaces WHERE name = '#{nms['name']}' AND id <> #{nms['max']}").map{|x| x["id"]}
+ execute("UPDATE projects SET namespace_id = #{nms["max"]} WHERE namespace_id IN(#{bad_nms_ids.join(', ')})")
+ execute("DELETE FROM namespaces WHERE id IN(#{bad_nms_ids.join(', ')})")
+ end
+ end
+
+ def down
+ # not implemented
+ end
+end
diff --git a/db/migrate/20150306023112_add_unique_index_to_namespace.rb b/db/migrate/20150306023112_add_unique_index_to_namespace.rb
new file mode 100644
index 0000000000..6472138e3e
--- /dev/null
+++ b/db/migrate/20150306023112_add_unique_index_to_namespace.rb
@@ -0,0 +1,9 @@
+class AddUniqueIndexToNamespace < ActiveRecord::Migration
+ def change
+ remove_index :namespaces, column: :name if index_exists?(:namespaces, :name)
+ remove_index :namespaces, column: :path if index_exists?(:namespaces, :path)
+
+ add_index :namespaces, :name, unique: true
+ add_index :namespaces, :path, unique: true
+ end
+end
diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb
new file mode 100644
index 0000000000..a1d4d9dedc
--- /dev/null
+++ b/db/migrate/20150313012111_create_subscriptions_table.rb
@@ -0,0 +1,16 @@
+class CreateSubscriptionsTable < ActiveRecord::Migration
+ def change
+ create_table :subscriptions do |t|
+ t.integer :user_id
+ t.references :subscribable, polymorphic: true
+ t.boolean :subscribed
+
+ t.timestamps
+ end
+
+ add_index :subscriptions,
+ [:subscribable_id, :subscribable_type, :user_id],
+ unique: true,
+ name: 'subscriptions_user_id_and_ref_fields'
+ end
+end
diff --git a/db/migrate/20150320234437_add_location_to_user.rb b/db/migrate/20150320234437_add_location_to_user.rb
new file mode 100644
index 0000000000..32731d37d7
--- /dev/null
+++ b/db/migrate/20150320234437_add_location_to_user.rb
@@ -0,0 +1,5 @@
+class AddLocationToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :location, :string
+ end
+end
diff --git a/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb b/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb
new file mode 100644
index 0000000000..42dc8173e4
--- /dev/null
+++ b/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb
@@ -0,0 +1,6 @@
+class SetIncorrectAssigneeIdToNull < ActiveRecord::Migration
+ def up
+ execute "UPDATE issues SET assignee_id = NULL WHERE assignee_id = -1"
+ execute "UPDATE merge_requests SET assignee_id = NULL WHERE assignee_id = -1"
+ end
+end
diff --git a/db/migrate/20150327122227_add_public_to_key.rb b/db/migrate/20150327122227_add_public_to_key.rb
new file mode 100644
index 0000000000..6ffbf4cda1
--- /dev/null
+++ b/db/migrate/20150327122227_add_public_to_key.rb
@@ -0,0 +1,5 @@
+class AddPublicToKey < ActiveRecord::Migration
+ def change
+ add_column :keys, :public, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20150327150017_add_import_data_to_project.rb b/db/migrate/20150327150017_add_import_data_to_project.rb
new file mode 100644
index 0000000000..12c00339ee
--- /dev/null
+++ b/db/migrate/20150327150017_add_import_data_to_project.rb
@@ -0,0 +1,5 @@
+class AddImportDataToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :import_data, :text
+ end
+end
diff --git a/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb b/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb
new file mode 100644
index 0000000000..1d161674a9
--- /dev/null
+++ b/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddMaxAttachmentSizeToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :max_attachment_size, :integer, default: 10, null: false
+ end
+end
diff --git a/db/migrate/20150406133311_add_invite_data_to_member.rb b/db/migrate/20150406133311_add_invite_data_to_member.rb
new file mode 100644
index 0000000000..3452fd45c4
--- /dev/null
+++ b/db/migrate/20150406133311_add_invite_data_to_member.rb
@@ -0,0 +1,12 @@
+class AddInviteDataToMember < ActiveRecord::Migration
+ def change
+ add_column :members, :created_by_id, :integer
+ add_column :members, :invite_email, :string
+ add_column :members, :invite_token, :string
+ add_column :members, :invite_accepted_at, :datetime
+
+ change_column :members, :user_id, :integer, null: true
+
+ add_index :members, :invite_token, unique: true
+ end
+end
diff --git a/db/migrate/20150411000035_fix_identities.rb b/db/migrate/20150411000035_fix_identities.rb
new file mode 100644
index 0000000000..d9051f9fff
--- /dev/null
+++ b/db/migrate/20150411000035_fix_identities.rb
@@ -0,0 +1,45 @@
+class FixIdentities < ActiveRecord::Migration
+ def up
+ # Up until now, legacy 'ldap' references in the database were charitably
+ # interpreted to point to the first LDAP server specified in the GitLab
+ # configuration. So if the database said 'provider: ldap' but the first
+ # LDAP server was called 'ldapmain', then we would try to interpret
+ # 'provider: ldap' as if it said 'provider: ldapmain'. This migration (and
+ # accompanying changes in the GitLab LDAP code) get rid of this complicated
+ # behavior. Any database references to 'provider: ldap' get rewritten to
+ # whatever the code would have interpreted it as, i.e. as a reference to
+ # the first LDAP server specified in gitlab.yml / gitlab.rb.
+ new_provider = if Gitlab.config.ldap.enabled
+ first_ldap_server = Gitlab.config.ldap.servers.values.first
+ first_ldap_server['provider_name']
+ else
+ 'ldapmain'
+ end
+
+ # Delete duplicate identities
+ # We use a sort of self-join to find rows in identities which match on
+ # user_id but where one has provider 'ldap'. We delete the duplicate row
+ # with provider 'ldap'.
+ delete_statement = ''
+ case adapter_name.downcase
+ when /^mysql/
+ delete_statement << 'DELETE FROM id1 USING identities AS id1, identities AS id2'
+ when 'postgresql'
+ delete_statement << 'DELETE FROM identities AS id1 USING identities AS id2'
+ else
+ raise "Unknown DB adapter: #{adapter_name}"
+ end
+ delete_statement << " WHERE id1.user_id = id2.user_id AND id1.provider = 'ldap' AND id2.provider = '#{new_provider}'"
+ execute delete_statement
+
+ # Update legacy identities
+ execute "UPDATE identities SET provider = '#{new_provider}' WHERE provider = 'ldap'"
+
+ if table_exists?('ldap_group_links')
+ execute "UPDATE ldap_group_links SET provider = '#{new_provider}' WHERE provider IS NULL OR provider = 'ldap'"
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20150411180045_rename_buildbox_service.rb b/db/migrate/20150411180045_rename_buildbox_service.rb
new file mode 100644
index 0000000000..5a0b5d07e5
--- /dev/null
+++ b/db/migrate/20150411180045_rename_buildbox_service.rb
@@ -0,0 +1,9 @@
+class RenameBuildboxService < ActiveRecord::Migration
+ def up
+ execute "UPDATE services SET type = 'BuildkiteService' WHERE type = 'BuildboxService';"
+ end
+
+ def down
+ execute "UPDATE services SET type = 'BuildboxService' WHERE type = 'BuildkiteService';"
+ end
+end
diff --git a/db/migrate/20150413192223_add_public_email_to_users.rb b/db/migrate/20150413192223_add_public_email_to_users.rb
new file mode 100644
index 0000000000..700e9f343a
--- /dev/null
+++ b/db/migrate/20150413192223_add_public_email_to_users.rb
@@ -0,0 +1,5 @@
+class AddPublicEmailToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :public_email, :string, default: "", null: false
+ end
+end
diff --git a/db/migrate/20150417121913_create_project_import_data.rb b/db/migrate/20150417121913_create_project_import_data.rb
new file mode 100644
index 0000000000..c78f5fde85
--- /dev/null
+++ b/db/migrate/20150417121913_create_project_import_data.rb
@@ -0,0 +1,8 @@
+class CreateProjectImportData < ActiveRecord::Migration
+ def change
+ create_table :project_import_data do |t|
+ t.references :project
+ t.text :data
+ end
+ end
+end
diff --git a/db/migrate/20150417122318_remove_import_data_from_project.rb b/db/migrate/20150417122318_remove_import_data_from_project.rb
new file mode 100644
index 0000000000..c275b49d22
--- /dev/null
+++ b/db/migrate/20150417122318_remove_import_data_from_project.rb
@@ -0,0 +1,5 @@
+class RemoveImportDataFromProject < ActiveRecord::Migration
+ def change
+ remove_column :projects, :import_data
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9159556ac7..1aee37b2e6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,11 +11,26 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20140730111702) do
+ActiveRecord::Schema.define(version: 20150417122318) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
+ create_table "application_settings", force: true do |t|
+ t.integer "default_projects_limit"
+ t.boolean "signup_enabled"
+ t.boolean "signin_enabled"
+ t.boolean "gravatar_enabled"
+ t.text "sign_in_text"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "home_page_url"
+ t.integer "default_branch_protection", default: 2
+ t.boolean "twitter_sharing_enabled", default: true
+ t.text "restricted_visibility_levels"
+ t.integer "max_attachment_size", default: 10, null: false
+ end
+
create_table "broadcast_messages", force: true do |t|
t.text "message", null: false
t.datetime "starts_at"
@@ -74,6 +89,17 @@ ActiveRecord::Schema.define(version: 20140730111702) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
+ create_table "identities", force: true do |t|
+ t.string "extern_uid"
+ t.string "provider"
+ t.integer "user_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "identities", ["created_at", "id"], name: "index_identities_on_created_at_and_id", using: :btree
+ add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
+
create_table "issues", force: true do |t|
t.string "title"
t.integer "assignee_id"
@@ -91,6 +117,7 @@ ActiveRecord::Schema.define(version: 20140730111702) do
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
+ add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree
add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
@@ -105,8 +132,10 @@ ActiveRecord::Schema.define(version: 20140730111702) do
t.string "title"
t.string "type"
t.string "fingerprint"
+ t.boolean "public", default: false, null: false
end
+ add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree
add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
create_table "label_links", force: true do |t|
@@ -130,6 +159,28 @@ ActiveRecord::Schema.define(version: 20140730111702) do
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
+ create_table "members", force: true do |t|
+ t.integer "access_level", null: false
+ t.integer "source_id", null: false
+ t.string "source_type", null: false
+ t.integer "user_id"
+ t.integer "notification_level", null: false
+ t.string "type"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "created_by_id"
+ t.string "invite_email"
+ t.string "invite_token"
+ t.datetime "invite_accepted_at"
+ end
+
+ add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree
+ add_index "members", ["created_at", "id"], name: "index_members_on_created_at_and_id", using: :btree
+ add_index "members", ["invite_token"], name: "index_members_on_invite_token", unique: true, using: :btree
+ add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree
+ add_index "members", ["type"], name: "index_members_on_type", using: :btree
+ add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
+
create_table "merge_request_diffs", force: true do |t|
t.string "state"
t.text "st_commits"
@@ -157,10 +208,12 @@ ActiveRecord::Schema.define(version: 20140730111702) do
t.integer "iid"
t.text "description"
t.integer "position", default: 0
+ t.datetime "locked_at"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
+ add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree
add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
@@ -180,6 +233,7 @@ ActiveRecord::Schema.define(version: 20140730111702) do
t.integer "iid"
end
+ add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree
add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
@@ -195,9 +249,10 @@ ActiveRecord::Schema.define(version: 20140730111702) do
t.string "avatar"
end
- add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree
+ add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
+ add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
- add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree
+ add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
create_table "notes", force: true do |t|
@@ -217,6 +272,7 @@ ActiveRecord::Schema.define(version: 20140730111702) do
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
+ add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
@@ -224,6 +280,54 @@ ActiveRecord::Schema.define(version: 20140730111702) do
add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
+ create_table "oauth_access_grants", force: true do |t|
+ t.integer "resource_owner_id", null: false
+ t.integer "application_id", null: false
+ t.string "token", null: false
+ t.integer "expires_in", null: false
+ t.text "redirect_uri", null: false
+ t.datetime "created_at", null: false
+ t.datetime "revoked_at"
+ t.string "scopes"
+ end
+
+ add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
+
+ create_table "oauth_access_tokens", force: true do |t|
+ t.integer "resource_owner_id"
+ t.integer "application_id"
+ t.string "token", null: false
+ t.string "refresh_token"
+ t.integer "expires_in"
+ t.datetime "revoked_at"
+ t.datetime "created_at", null: false
+ t.string "scopes"
+ end
+
+ add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree
+ add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree
+ add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
+
+ create_table "oauth_applications", force: true do |t|
+ t.string "name", null: false
+ t.string "uid", null: false
+ t.string "secret", null: false
+ t.text "redirect_uri", null: false
+ t.string "scopes", default: "", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "owner_id"
+ t.string "owner_type"
+ end
+
+ add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
+ add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
+
+ create_table "project_import_data", force: true do |t|
+ t.integer "project_id"
+ t.text "data"
+ end
+
create_table "projects", force: true do |t|
t.string "name"
t.string "path"
@@ -243,21 +347,26 @@ ActiveRecord::Schema.define(version: 20140730111702) do
t.string "import_url"
t.integer "visibility_level", default: 0, null: false
t.boolean "archived", default: false, null: false
+ t.string "avatar"
t.string "import_status"
t.float "repository_size", default: 0.0
t.integer "star_count", default: 0, null: false
+ t.string "import_type"
+ t.string "import_source"
end
+ add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
create_table "protected_branches", force: true do |t|
- t.integer "project_id", null: false
- t.string "name", null: false
+ t.integer "project_id", null: false
+ t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.boolean "developers_can_push", default: false, null: false
end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
@@ -265,37 +374,52 @@ ActiveRecord::Schema.define(version: 20140730111702) do
create_table "services", force: true do |t|
t.string "type"
t.string "title"
- t.string "token"
- t.integer "project_id", null: false
+ t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "active", default: false, null: false
- t.string "project_url"
- t.string "subdomain"
- t.string "room"
- t.text "recipients"
- t.string "api_key"
+ t.boolean "active", default: false, null: false
+ t.text "properties"
+ t.boolean "template", default: false
+ t.boolean "push_events", default: true
+ t.boolean "issues_events", default: true
+ t.boolean "merge_requests_events", default: true
+ t.boolean "tag_push_events", default: true
+ t.boolean "note_events", default: true, null: false
end
+ add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
create_table "snippets", force: true do |t|
t.string "title"
t.text "content"
- t.integer "author_id", null: false
+ t.integer "author_id", null: false
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "file_name"
t.datetime "expires_at"
- t.boolean "private", default: true, null: false
t.string "type"
+ t.integer "visibility_level", default: 0, null: false
end
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
+ add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree
add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
+ add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
+
+ create_table "subscriptions", force: true do |t|
+ t.integer "user_id"
+ t.integer "subscribable_id"
+ t.string "subscribable_type"
+ t.boolean "subscribed"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree
create_table "taggings", force: true do |t|
t.integer "tag_id"
@@ -315,12 +439,12 @@ ActiveRecord::Schema.define(version: 20140730111702) do
end
create_table "users", force: true do |t|
- t.string "email", default: "", null: false
- t.string "encrypted_password", default: "", null: false
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", default: 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
@@ -328,70 +452,53 @@ ActiveRecord::Schema.define(version: 20140730111702) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
- t.boolean "admin", default: false, null: false
- t.integer "projects_limit", default: 10
- t.string "skype", default: "", null: false
- t.string "linkedin", default: "", null: false
- t.string "twitter", default: "", null: false
+ t.boolean "admin", default: false, null: false
+ t.integer "projects_limit", default: 10
+ t.string "skype", default: "", null: false
+ t.string "linkedin", default: "", null: false
+ t.string "twitter", default: "", null: false
t.string "authentication_token"
- t.integer "theme_id", default: 1, null: false
+ t.integer "theme_id", default: 1, null: false
t.string "bio"
- t.integer "failed_attempts", default: 0
+ t.integer "failed_attempts", default: 0
t.datetime "locked_at"
- t.string "extern_uid"
- t.string "provider"
t.string "username"
- t.boolean "can_create_group", default: true, null: false
- t.boolean "can_create_team", default: true, null: false
+ t.boolean "can_create_group", default: true, null: false
+ t.boolean "can_create_team", default: true, null: false
t.string "state"
- t.integer "color_scheme_id", default: 1, null: false
- t.integer "notification_level", default: 1, null: false
+ t.integer "color_scheme_id", default: 1, null: false
+ t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
+ t.datetime "last_credential_check_at"
t.string "avatar"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
- t.boolean "hide_no_ssh_key", default: false
- t.string "website_url", default: "", null: false
- t.datetime "last_credential_check_at"
+ t.boolean "hide_no_ssh_key", default: false
+ t.string "website_url", default: "", null: false
+ t.string "github_access_token"
+ t.string "gitlab_access_token"
+ t.string "notification_email"
+ t.boolean "hide_no_password", default: false
+ t.boolean "password_automatically_set", default: false
+ t.string "bitbucket_access_token"
+ t.string "bitbucket_access_token_secret"
+ t.string "location"
+ t.string "public_email", default: "", null: false
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
+ add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree
add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
- add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
- create_table "users_groups", force: true do |t|
- t.integer "group_access", null: false
- t.integer "group_id", null: false
- t.integer "user_id", null: false
- t.datetime "created_at"
- t.datetime "updated_at"
- t.integer "notification_level", default: 3, null: false
- end
-
- add_index "users_groups", ["user_id"], name: "index_users_groups_on_user_id", using: :btree
-
- create_table "users_projects", force: true do |t|
- t.integer "user_id", null: false
- t.integer "project_id", null: false
- t.datetime "created_at"
- t.datetime "updated_at"
- t.integer "project_access", default: 0, null: false
- t.integer "notification_level", default: 3, null: false
- end
-
- add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree
- add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree
- add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree
-
create_table "users_star_projects", force: true do |t|
t.integer "project_id", null: false
t.integer "user_id", null: false
@@ -416,6 +523,7 @@ ActiveRecord::Schema.define(version: 20140730111702) do
t.boolean "tag_push_events", default: false
end
+ add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
end
diff --git a/doc/README.md b/doc/README.md
index f05078ee38..4e00dceac2 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -2,22 +2,30 @@
## User documentation
-- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
-- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
+- [API](api/README.md) Automate GitLab via a simple and powerful API.
+- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
-- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
+- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
+- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
-- [Workflow](workflow/README.md) Learn how to use Git and GitLab together.
+- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
+- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
## Administrator documentation
-- [Install](install/README.md) Requirements, directory structures and manual installation.
+- [Install](install/README.md) Requirements, directory structures and installation from source.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
-- [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
-- [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
+- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
+- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
+- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [Update](update/README.md) Update guides to upgrade your installation.
+- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
+- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Operations](operations/README.md) Keeping GitLab up and running
+- [Log system](logs/logs.md) Log system
## Contributor documentation
diff --git a/doc/api/README.md b/doc/api/README.md
index ababb7b699..dec530d0b8 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -21,13 +21,8 @@
## Clients
-- [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
-- [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com)
-- [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
-- [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
-- [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
-- [node-gitlab](https://github.com/moul/node-gitlab) - Node.js
-- [NGitLab](https://github.com/Scooletz/NGitLab) - .NET
+Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients).
+You can use [GitLab as an OAuth2 client](oauth2.md) to make API calls.
## Introduction
@@ -57,6 +52,24 @@ curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/p
The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL.
+## Authentication with OAuth2 token
+
+Instead of the private_token you can transmit the OAuth2 access token as a header or as a parameter.
+
+### OAuth2 token (as a parameter)
+
+```
+curl https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+```
+
+### OAuth2 token (as a header)
+
+```
+curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+```
+
+Read more about [GitLab as an OAuth2 client](oauth2.md).
+
## Status codes
The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave.
@@ -80,11 +93,12 @@ Return values:
- `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
- `405 Method Not Allowed` - The request is not supported
- `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
+- `422 Unprocessable` - The entity could not be processed
- `500 Server Error` - While handling the request something went wrong on the server side
## Sudo
-All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by url or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
+All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by URL or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
If a non administrative `private_token` is provided then an error message will be returned with status code 403:
@@ -129,7 +143,7 @@ When listing resources you can pass the following parameters:
- `page` (default: `1`) - page number
- `per_page` (default: `20`, max: `100`) - number of items to list per page
-[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own urls.
+[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own URLs.
## id vs iid
@@ -144,3 +158,52 @@ Issue:
- iid - is unique only in scope of a single project. When you browse issues or merge requests with Web UI, you see iid.
So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`. But when you want to create a link to web page - use `http:://host/project/issues/:iid.json`
+
+## Data validation and error reporting
+
+When working with the API you may encounter validation errors. In such case, the API will answer with an HTTP `400` status.
+Such errors appear in two cases:
+
+* A required attribute of the API request is missing, e.g. the title of an issue is not given
+* An attribute did not pass the validation, e.g. user bio is too long
+
+When an attribute is missing, you will get something like:
+
+ HTTP/1.1 400 Bad Request
+ Content-Type: application/json
+
+ {
+ "message":"400 (Bad request) \"title\" not given"
+ }
+
+When a validation error occurs, error messages will be different. They will hold all details of validation errors:
+
+ HTTP/1.1 400 Bad Request
+ Content-Type: application/json
+
+ {
+ "message": {
+ "bio": [
+ "is too long (maximum is 255 characters)"
+ ]
+ }
+ }
+
+This makes error messages more machine-readable. The format can be described as follow:
+
+ {
+ "message": {
+ "": [
+ "",
+ "",
+ ...
+ ],
+ "": {
+ "": [
+ "",
+ "",
+ ...
+ ],
+ }
+ }
+ }
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 31469b6fe9..6a9c10c852 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -15,27 +15,20 @@ Parameters:
```json
[
{
- "name": "master",
"commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
+ "author_email": "john@example.com",
+ "author_name": "John Smith",
"authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": true
}
]
@@ -56,27 +49,20 @@ Parameters:
```json
{
- "name": "master",
"commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
+ "author_email": "john@example.com",
+ "author_name": "John Smith",
"authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": true
}
```
@@ -97,27 +83,20 @@ Parameters:
```json
{
- "name": "master",
"commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
+ "author_email": "john@example.com",
+ "author_name": "John Smith",
"authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": true
}
```
@@ -138,27 +117,20 @@ Parameters:
```json
{
- "name": "master",
"commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
+ "author_email": "john@example.com",
+ "author_name": "John Smith",
"authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": false
}
```
@@ -177,25 +149,26 @@ Parameters:
```json
{
- "name": "my-new-branch",
"commit": {
- "id": "8848c0e90327a0b70f1865b843fb2fbfb9345e57",
- "message": "Merge pull request #54 from brightbox/use_fog_brightbox_module\n\nUpdate to use fog-brightbox module",
- "parent_ids": [
- "fff449e0bf453576f16c91d6544f00a2664009d8",
- "f93a93626fec20fd659f4ed3ab2e64019b6169ae"
- ],
- "authored_date": "2014-02-20T19:54:55+02:00",
- "author_name": "john smith",
"author_email": "john@example.com",
- "committed_date": "2014-02-20T19:54:55+02:00",
- "committer_name": "john smith",
- "committer_email": "john@example.com"
+ "author_name": "John Smith",
+ "authored_date": "2012-06-27T05:51:39-07:00",
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": false
}
```
+It return 200 if succeed or 400 if failed with error message explaining reason.
+
## Delete repository branch
```
@@ -207,4 +180,13 @@ Parameters:
- `id` (required) - The ID of a project
- `branch` (required) - The name of the branch
-It return 200 if succeed or 405 if failed with error message explaining reason.
+It return 200 if succeed, 404 if the branch to be deleted does not exist
+or 400 for other reasons. In case of an error, an explaining message is provided.
+
+Success response:
+
+```json
+{
+ "branch_name": "my-removed-branch"
+}
+```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 9475ecbaa6..eb8d6a4359 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -93,3 +93,66 @@ Parameters:
}
]
```
+
+## Get the comments of a commit
+
+Get the comments of a commit in a project.
+
+```
+GET /projects/:id/repository/commits/:sha/comments
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+
+```json
+[
+ {
+ "note": "this code is really nice",
+ "author": {
+ "id": 11,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2014-03-06T08:17:35.000Z"
+ }
+ }
+]
+```
+
+## Post comment to commit
+
+Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required.
+
+```
+POST /projects/:id/repository/commits/:sha/comments
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+- `note` (required) - Text of comment
+- `path` (optional) - The file path
+- `line` (optional) - The line number
+- `line_type` (optional) - The line type (new or old)
+
+```json
+{
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "blocked": false,
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "note": "text1",
+ "path": "example.rb",
+ "line": 5,
+ "line_type": "new"
+}
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 6b379b02d2..b5a4b05cca 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -14,11 +14,13 @@ GET /groups
"id": 1,
"name": "Foobar Group",
"path": "foo-bar",
- "owner_id": 18
+ "description": "An interesting group"
}
]
```
+You can search for groups by name or path, see below.
+
## Details of a group
Get all details of a group.
@@ -29,7 +31,7 @@ GET /groups/:id
Parameters:
-- `id` (required) - The ID of a group
+- `id` (required) - The ID or path of a group
## New group
@@ -43,6 +45,7 @@ Parameters:
- `name` (required) - The name of the group
- `path` (required) - The path of the group
+- `description` (optional) - The group's description
## Transfer project to group
@@ -54,7 +57,7 @@ POST /groups/:id/projects/:project_id
Parameters:
-- `id` (required) - The ID of a group
+- `id` (required) - The ID or path of a group
- `project_id` (required) - The ID of a project
## Remove group
@@ -67,7 +70,26 @@ DELETE /groups/:id
Parameters:
-- `id` (required) - The ID of a user group
+- `id` (required) - The ID or path of a user group
+
+## Search for group
+
+Get all groups that match your string in their name or path.
+
+```
+GET /groups?search=foobar
+```
+
+```json
+[
+ {
+ "id": 1,
+ "name": "Foobar Group",
+ "path": "foo-bar",
+ "description": "An interesting group"
+ }
+]
+```
## Group members
@@ -124,10 +146,24 @@ POST /groups/:id/members
Parameters:
-- `id` (required) - The ID of a group
+- `id` (required) - The ID or path of a group
- `user_id` (required) - The ID of a user to add
- `access_level` (required) - Project access level
+### Edit group team member
+
+Updates a group team member to a specified access level.
+
+```
+PUT /groups/:id/members/:user_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a group
+- `user_id` (required) - The ID of a group member
+- `access_level` (required) - Project access level
+
### Remove user team member
Removes user from user team.
@@ -138,5 +174,5 @@ DELETE /groups/:id/members/:user_id
Parameters:
-- `id` (required) - The ID of a user group
+- `id` (required) - The ID or path of a user group
- `user_id` (required) - The ID of a group member
diff --git a/doc/api/issues.md b/doc/api/issues.md
index a4b3b3e991..a7dd8b74c3 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -7,8 +7,20 @@ Get all issues created by authenticated user. This function takes pagination par
```
GET /issues
+GET /issues?state=opened
+GET /issues?state=closed
+GET /issues?labels=foo
+GET /issues?labels=foo,bar
+GET /issues?labels=foo,bar&state=opened
```
+Parameters:
+
+- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
+- `labels` (optional) - Comma-separated list of label names
+- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+
```json
[
{
@@ -46,7 +58,7 @@ GET /issues
"title": "v1.0",
"description": "",
"due_date": "2012-07-20",
- "state": "reopenend",
+ "state": "reopened",
"updated_at": "2012-07-04T13:42:48Z",
"created_at": "2012-07-04T13:42:48Z"
},
@@ -80,11 +92,23 @@ to return the list of project issues.
```
GET /projects/:id/issues
+GET /projects/:id/issues?state=opened
+GET /projects/:id/issues?state=closed
+GET /projects/:id/issues?labels=foo
+GET /projects/:id/issues?labels=foo,bar
+GET /projects/:id/issues?labels=foo,bar&state=opened
+GET /projects/:id/issues?milestone=1.0.0
+GET /projects/:id/issues?milestone=1.0.0&state=opened
```
Parameters:
- `id` (required) - The ID of a project
+- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
+- `labels` (optional) - Comma-separated list of label names
+- `milestone` (optional) - Milestone title
+- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
## Single issue
@@ -184,7 +208,7 @@ If an error occurs, an error number and a message explaining the reason is retur
## Delete existing issue (**Deprecated**)
-The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `closed` set to 1.
+The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `state_event` set to `close`.
```
DELETE /projects/:id/issues/:issue_id
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 3616e29ef7..6a272539e4 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -2,7 +2,9 @@
## List merge requests
-Get all merge requests for this project. The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
+Get all merge requests for this project.
+The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`).
+The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
```
GET /projects/:id/merge_requests
@@ -14,6 +16,8 @@ Parameters:
- `id` (required) - The ID of a project
- `state` (optional) - Return `all` requests or just those that are `merged`, `opened` or `closed`
+- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
```json
[
@@ -92,6 +96,76 @@ Parameters:
}
```
+## Get single MR changes
+
+Shows information about the merge request including its files and changes
+
+```
+GET /projects/:id/merge_request/:merge_request_id/changes
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `merge_request_id` (required) - The ID of MR
+
+```json
+{
+ "id": 21,
+ "iid": 1,
+ "project_id": 4,
+ "title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.",
+ "description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.",
+ "state": "reopened",
+ "created_at": "2015-02-02T19:49:39.159Z",
+ "updated_at": "2015-02-02T20:08:49.959Z",
+ "target_branch": "secret_token",
+ "source_branch": "version-1-9",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "name": "Chad Hamill",
+ "username": "jarrett",
+ "id": 5,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/b95567800f828948baf5f4160ebb2473?s=40&d=identicon"
+ },
+ "assignee": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40&d=identicon"
+ },
+ "source_project_id": 4,
+ "target_project_id": 4,
+ "labels": [ ],
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 4,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "files": [
+ {
+ "old_path": "VERSION",
+ "new_path": "VERSION",
+ "a_mode": "100644",
+ "b_mode": "100644",
+ "diff": "--- a/VERSION\ +++ b/VERSION\ @@ -1 +1 @@\ -1.9.7\ +1.9.8",
+ "new_file": false,
+ "renamed_file": false,
+ "deleted_file": false
+ }
+ ]
+}
+```
+
## Create MR
Creates a new merge request.
@@ -107,6 +181,7 @@ Parameters:
- `target_branch` (required) - The target branch
- `assignee_id` (optional) - Assignee user ID
- `title` (required) - Title of MR
+- `description` (optional) - Description of MR
- `target_project_id` (optional) - The target project (numeric id)
```json
@@ -158,6 +233,7 @@ Parameters:
- `target_branch` - The target branch
- `assignee_id` - Assignee user ID
- `title` - Title of MR
+- `description` - Description of MR
- `state_event` - New state (close|reopen|merge)
```json
@@ -167,6 +243,7 @@ Parameters:
"source_branch": "test1",
"project_id": 3,
"title": "test1",
+ "description": "description1",
"state": "opened",
"upvotes": 0,
"downvotes": 0,
@@ -298,7 +375,7 @@ Parameters:
}
},
{
- "note": "_Status changed to closed_",
+ "note": "Status changed to closed",
"author": {
"id": 11,
"username": "admin",
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 2f52532750..d48b3bcce8 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -72,3 +72,16 @@ Parameters:
- `description` (optional) - The description of a milestone
- `due_date` (optional) - The due date of the milestone
- `state_event` (optional) - The state event of the milestone (close|activate)
+
+## Get all issues assigned to a single milestone
+
+Gets all issues assigned to a single project milestone.
+
+```
+GET /projects/:id/milestones/:milestone_id/issues
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `milestone_id` (required) - The ID of a project milestone
diff --git a/doc/api/notes.md b/doc/api/notes.md
index b5256ac803..ee2f9fa0ea 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -21,7 +21,7 @@ Parameters:
[
{
"id": 302,
- "body": "_Status changed to closed_",
+ "body": "Status changed to closed",
"attachment": null,
"author": {
"id": 1,
@@ -78,6 +78,21 @@ Parameters:
- `issue_id` (required) - The ID of an issue
- `body` (required) - The content of a note
+### Modify existing issue note
+
+Modify existing note of an issue.
+
+```
+PUT /projects/:id/issues/:issue_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `issue_id` (required) - The ID of an issue
+- `note_id` (required) - The ID of a note
+- `body` (required) - The content of a note
+
## Snippets
### List all snippet notes
@@ -137,7 +152,22 @@ POST /projects/:id/snippets/:snippet_id/notes
Parameters:
- `id` (required) - The ID of a project
-- `snippet_id` (required) - The ID of an snippet
+- `snippet_id` (required) - The ID of a snippet
+- `body` (required) - The content of a note
+
+### Modify existing snippet note
+
+Modify existing note of a snippet.
+
+```
+PUT /projects/:id/snippets/:snippet_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `snippet_id` (required) - The ID of a snippet
+- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
## Merge Requests
@@ -199,3 +229,18 @@ Parameters:
- `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of a merge request
- `body` (required) - The content of a note
+
+### Modify existing merge request note
+
+Modify existing note of a merge request.
+
+```
+PUT /projects/:id/merge_requests/:merge_request_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `merge_request_id` (required) - The ID of a merge request
+- `note_id` (required) - The ID of a note
+- `body` (required) - The content of a note
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
new file mode 100644
index 0000000000..d416a826f7
--- /dev/null
+++ b/doc/api/oauth2.md
@@ -0,0 +1,102 @@
+# GitLab as an OAuth2 client
+
+This document is about using other OAuth authentication service providers to sign into GitLab.
+If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md).
+
+OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password.
+
+Before using the OAuth2 you should create an application in user's account. Each application gets a unique App ID and App Secret parameters. You should not share these.
+
+This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper)
+
+## Web Application Flow
+
+This flow is using for authentication from third-party web sites and is probably used the most.
+It basically consists of an exchange of an authorization token for an access token. For more detailed info, check out the [RFC spec here](http://tools.ietf.org/html/rfc6749#section-4.1)
+
+This flow consists from 3 steps.
+
+### 1. Registering the client
+
+Create an application in user's account profile.
+
+### 2. Requesting authorization
+
+To request the authorization token, you should visit the `/oauth/authorize` endpoint. You can do that by visiting manually the URL:
+
+```
+http://localhost:3000/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code
+```
+
+Where REDIRECT_URI is the URL in your app where users will be sent after authorization.
+
+### 3. Requesting the access token
+
+To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. In this case, I used rest-client:
+
+```
+parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI'
+RestClient.post 'http://localhost:3000/oauth/token', parameters
+
+# The response will be
+{
+ "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
+ "token_type": "bearer",
+ "expires_in": 7200,
+ "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
+}
+```
+
+You can now make requests to the API with the access token returned.
+
+### Use the access token to access the API
+
+The access token allows you to make requests to the API on a behalf of a user.
+
+```
+GET https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+```
+
+Or you can put the token to the Authorization header:
+
+```
+curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+```
+
+## Resource Owner Password Credentials
+
+In this flow, a token is requested in exchange for the resource owner credentials (username and password).
+The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the
+client is part of the device operating system or a highly privileged application), and when other authorization grant types are not
+available (such as an authorization code).
+
+Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used
+for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the
+resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token.
+You can do POST request to `/oauth/token` with parameters:
+
+```
+{
+ "grant_type" : "password",
+ "username" : "user@example.com",
+ "password" : "sekret"
+}
+```
+
+Then, you'll receive the access token back in the response:
+
+```
+{
+ "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
+ "token_type": "bearer",
+ "expires_in": 7200
+}
+```
+
+For testing you can use the oauth2 ruby gem:
+
+```
+client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com")
+access_token = client.password.get_token('user@example.com', 'sekret')
+puts access_token.token
+```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 8995551b9e..971fe96fb8 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1,5 +1,23 @@
# Projects
+
+### Project visibility level
+
+Project in GitLab has be either private, internal or public.
+You can determine it by `visibility_level` field in project.
+
+Constants for project visibility levels are next:
+
+* Private. `visibility_level` is `0`.
+ Project access must be granted explicitly for each user.
+
+* Internal. `visibility_level` is `10`.
+ The project can be cloned by any logged in user.
+
+* Public. `visibility_level` is `20`.
+ The project can be cloned without any authentication.
+
+
## List projects
Get a list of projects accessible by the authenticated user.
@@ -11,6 +29,9 @@ GET /projects
Parameters:
- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
```json
[
@@ -23,6 +44,10 @@ Parameters:
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
"web_url": "http://example.com/diaspora/diaspora-client",
+ "tag_list": [
+ "example",
+ "disapora client"
+ ],
"owner": {
"id": 3,
"name": "Diaspora",
@@ -38,6 +63,7 @@ Parameters:
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
+ "creator_id": 3,
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
@@ -47,7 +73,8 @@ Parameters:
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
},
- "archived": false
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
},
{
"id": 6,
@@ -58,6 +85,10 @@ Parameters:
"ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
"http_url_to_repo": "http://example.com/brightbox/puppet.git",
"web_url": "http://example.com/brightbox/puppet",
+ "tag_list": [
+ "example",
+ "puppet"
+ ],
"owner": {
"id": 4,
"name": "Brightbox",
@@ -73,6 +104,7 @@ Parameters:
"snippets_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
+ "creator_id": 3,
"namespace": {
"created_at": "2013-09-30T13:46:02Z",
"description": "",
@@ -82,7 +114,8 @@ Parameters:
"path": "brightbox",
"updated_at": "2013-09-30T13:46:02Z"
},
- "archived": false
+ "archived": false,
+ "avatar_url": null
}
]
```
@@ -95,6 +128,13 @@ Get a list of projects which are owned by the authenticated user.
GET /projects/owned
```
+Parameters:
+
+- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
+
### List ALL projects
Get a list of all GitLab projects (admin only).
@@ -103,6 +143,13 @@ Get a list of all GitLab projects (admin only).
GET /projects/all
```
+Parameters:
+
+- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
+
### Get single project
Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
@@ -126,6 +173,10 @@ Parameters:
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
"web_url": "http://example.com/diaspora/diaspora-project-site",
+ "tag_list": [
+ "example",
+ "disapora project"
+ ],
"owner": {
"id": 3,
"name": "Diaspora",
@@ -141,6 +192,7 @@ Parameters:
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
+ "creator_id": 3,
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
@@ -160,7 +212,8 @@ Parameters:
"notification_level": 3
}
},
- "archived": false
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png"
}
```
@@ -186,6 +239,7 @@ Parameters:
"target_id": 830,
"target_type": "Issue",
"author_id": 1,
+ "author_username": "john",
"data": null,
"target_title": "Public project search field"
},
@@ -196,6 +250,7 @@ Parameters:
"target_id": null,
"target_type": null,
"author_id": 1,
+ "author_username": "john",
"data": {
"before": "50d4420237a9de7be1304607147aec22e4a14af7",
"after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
@@ -231,6 +286,7 @@ Parameters:
"target_id": 840,
"target_type": "Issue",
"author_id": 1,
+ "author_username": "john",
"data": null,
"target_title": "Finish & merge Code search PR"
}
@@ -248,6 +304,7 @@ POST /projects
Parameters:
- `name` (required) - new project name
+- `path` (optional) - custom repository name for new project. By default generated based on name
- `namespace_id` (optional) - namespace for the new project (defaults to user)
- `description` (optional) - short project description
- `issues_enabled` (optional)
@@ -280,6 +337,43 @@ Parameters:
- `visibility_level` (optional)
- `import_url` (optional)
+### Edit project
+
+Updates an existing project
+
+```
+PUT /projects/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `name` (optional) - project name
+- `path` (optional) - repository name for project
+- `description` (optional) - short project description
+- `default_branch` (optional)
+- `issues_enabled` (optional)
+- `merge_requests_enabled` (optional)
+- `wiki_enabled` (optional)
+- `snippets_enabled` (optional)
+- `public` (optional) - if `true` same as setting visibility_level = 20
+- `visibility_level` (optional)
+
+On success, method returns 200 with the updated project. If parameters are
+invalid, 400 is returned.
+
+### Fork project
+
+Forks a project into the user namespace of the authenticated user.
+
+```
+POST /projects/fork/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID of the project to be forked
+
### Remove project
Removes a project including all associated resources (issues, merge requests etc.)
@@ -434,6 +528,7 @@ Parameters:
- `push_events` - Trigger hook on push events
- `issues_events` - Trigger hook on issues events
- `merge_requests_events` - Trigger hook on merge_requests events
+- `tag_push_events` - Trigger hook on push_tag events
### Edit project hook
@@ -451,6 +546,7 @@ Parameters:
- `push_events` - Trigger hook on push events
- `issues_events` - Trigger hook on issues events
- `merge_requests_events` - Trigger hook on merge_requests events
+- `tag_push_events` - Trigger hook on push_tag events
### Delete project hook
@@ -495,7 +591,7 @@ Parameters:
}
],
"tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
- "message": "give caolan credit where it's due (up top)",
+ "message": "give Caolan credit where it's due (up top)",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
@@ -610,6 +706,8 @@ GET /projects/search/:query
Parameters:
-- query (required) - A string contained in the project name
-- per_page (optional) - number of projects to return per page
-- page (optional) - the page to retrieve
+- `query` (required) - A string contained in the project name
+- `per_page` (optional) - number of projects to return per page
+- `page` (optional) - the page to retrieve
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 1074b78fd7..3316745380 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -15,24 +15,21 @@ Parameters:
```json
[
{
- "name": "v1.0.0",
"commit": {
- "id": "2695effb5807a22ff3d138d593fd856244e155e7",
- "parents": [],
- "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d",
- "message": "Initial commit",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "Jack Smith",
- "email": "jack@example.com"
- },
+ "author_name": "John Smith",
+ "author_email": "john@example.com",
"authored_date": "2012-05-28T04:42:42-07:00",
- "committed_date": "2012-05-28T04:42:42-07:00"
+ "committed_date": "2012-05-28T04:42:42-07:00",
+ "committer_name": "Jack Smith",
+ "committer_email": "jack@example.com",
+ "id": "2695effb5807a22ff3d138d593fd856244e155e7",
+ "message": "Initial commit",
+ "parents_ids": [
+ "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
+ ]
},
- "protected": null
+ "name": "v1.0.0",
+ "message": null
}
]
```
@@ -50,26 +47,32 @@ Parameters:
- `id` (required) - The ID of a project
- `tag_name` (required) - The name of a tag
- `ref` (required) - Create tag using commit SHA, another tag name, or branch name.
+- `message` (optional) - Creates annotated tag.
```json
-[
- {
- "name": "v1.0.0",
- "commit": {
- "id": "2695effb5807a22ff3d138d593fd856244e155e7",
- "parents": [],
- "message": "Initial commit",
- "authored_date": "2012-05-28T04:42:42-07:00",
- "author_name": "John Smith",
- "author email": "john@example.com",
- "committer_name": "Jack Smith",
- "committed_date": "2012-05-28T04:42:42-07:00",
- "committer_email": "jack@example.com"
- },
- "protected": false
- }
-]
+{
+ "commit": {
+ "author_name": "John Smith",
+ "author_email": "john@example.com",
+ "authored_date": "2012-05-28T04:42:42-07:00",
+ "committed_date": "2012-05-28T04:42:42-07:00",
+ "committer_name": "Jack Smith",
+ "committer_email": "jack@example.com",
+ "id": "2695effb5807a22ff3d138d593fd856244e155e7",
+ "message": "Initial commit",
+ "parents_ids": [
+ "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
+ ]
+ },
+ "name": "v1.0.0",
+ "message": null
+}
```
+The message will be `nil` when creating a lightweight tag otherwise
+it will contain the annotation.
+
+It returns 200 if the operation succeed. In case of an error,
+405 with an explaining error message is returned.
## List repository tree
diff --git a/doc/api/services.md b/doc/api/services.md
new file mode 100644
index 0000000000..cbf767d1b2
--- /dev/null
+++ b/doc/api/services.md
@@ -0,0 +1,46 @@
+# Services
+
+## GitLab CI
+
+### Edit GitLab CI service
+
+Set GitLab CI service for a project.
+
+```
+PUT /projects/:id/services/gitlab-ci
+```
+
+Parameters:
+
+- `token` (required) - CI project token
+- `project_url` (required) - CI project URL
+
+### Delete GitLab CI service
+
+Delete GitLab CI service settings for a project.
+
+```
+DELETE /projects/:id/services/gitlab-ci
+```
+
+## HipChat
+
+### Edit HipChat service
+
+Set HipChat service for project.
+
+```
+PUT /projects/:id/services/hipchat
+```
+Parameters:
+
+- `token` (required) - HipChat token
+- `room` (required) - HipChat room name
+
+### Delete HipChat service
+
+Delete HipChat service for a project.
+
+```
+DELETE /projects/:id/services/hipchat
+```
diff --git a/doc/api/users.md b/doc/api/users.md
index 3fdd3a75e8..a8b7685b50 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -78,7 +78,8 @@ GET /users
"is_admin": false,
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
]
```
@@ -140,7 +141,8 @@ Parameters:
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
```
@@ -168,6 +170,7 @@ Parameters:
- `bio` (optional) - User's biography
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
+- `confirm` (optional) - Require confirmation - true (default) or false
## User modification
@@ -240,7 +243,8 @@ GET /user
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
```
@@ -257,12 +261,14 @@ GET /user/keys
{
"id": 1,
"title": "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
},
{
"id": 3,
"title": "Another Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
}
]
```
@@ -299,7 +305,8 @@ Parameters:
{
"id": 1,
"title": "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
}
```
@@ -316,6 +323,31 @@ Parameters:
- `title` (required) - new SSH Key's title
- `key` (required) - new SSH key
+```json
+{
+ "created_at": "2015-01-21T17:44:33.512Z",
+ "key": "ssh-dss AAAAB3NzaC1kc3MAAACBAMLrhYgI3atfrSD6KDas1b/3n6R/HP+bLaHHX6oh+L1vg31mdUqK0Ac/NjZoQunavoyzqdPYhFz9zzOezCrZKjuJDS3NRK9rspvjgM0xYR4d47oNZbdZbwkI4cTv/gcMlquRy0OvpfIvJtjtaJWMwTLtM5VhRusRuUlpH99UUVeXAAAAFQCVyX+92hBEjInEKL0v13c/egDCTQAAAIEAvFdWGq0ccOPbw4f/F8LpZqvWDydAcpXHV3thwb7WkFfppvm4SZte0zds1FJ+Hr8Xzzc5zMHe6J4Nlay/rP4ewmIW7iFKNBEYb/yWa+ceLrs+TfR672TaAgO6o7iSRofEq5YLdwgrwkMmIawa21FrZ2D9SPao/IwvENzk/xcHu7YAAACAQFXQH6HQnxOrw4dqf0NqeKy1tfIPxYYUZhPJfo9O0AmBW2S36pD2l14kS89fvz6Y1g8gN/FwFnRncMzlLY/hX70FSc/3hKBSbH6C6j8hwlgFKfizav21eS358JJz93leOakJZnGb8XlWvz1UJbwCsnR2VEY8Dz90uIk1l/UqHkA= loic@call",
+ "title": "ABC",
+ "id": 4
+}
+```
+
+Will return created key with status `201 Created` on success. If an
+error occurs a `400 Bad Request` is returned with a message explaining the error:
+
+```json
+{
+ "message": {
+ "fingerprint": [
+ "has already been taken"
+ ],
+ "key": [
+ "has already been taken"
+ ]
+ }
+}
+```
+
## Add SSH key for user
Create new key owned by specified user. Available only for admin
diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md
new file mode 100644
index 0000000000..aa65a082a5
--- /dev/null
+++ b/doc/customization/issue_closing.md
@@ -0,0 +1,36 @@
+# Issue closing pattern
+
+If a commit message matches the regular expression below, all issues referenced from
+the matched text will be closed. This happens when the commit is pushed or merged
+into the default branch of a project.
+
+When not specified, the default issue_closing_pattern as shown below will be used:
+
+```bash
+((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)
+```
+
+For example:
+
+```
+git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes #22). This commit is also related to #17 and fixes #18, #19 and #23."
+```
+
+will close `#20`, `#21`, `#22`, `#18`, `#19` and `#23`, but `#17` won't be closed
+as it does not match the pattern. It also works with multiline commit messages.
+
+Tip: you can test this closing pattern at [http://rubular.com][1]. Use this site
+to test your own patterns.
+
+## Change the pattern
+
+For Omnibus installs you can change the default pattern in `/etc/gitlab/gitlab.rb`:
+
+```
+issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)'
+```
+
+For manual installs you can customize the pattern in [gitlab.yml][0].
+
+[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/40c3675372320febf5264061c9bcd63db2dfd13c/config/gitlab.yml.example#L65
+[1]: http://rubular.com/r/Xmbexed1OJ
diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md
new file mode 100644
index 0000000000..ee57fdc659
--- /dev/null
+++ b/doc/customization/libravatar.md
@@ -0,0 +1,69 @@
+# Use Libravatar service with GitLab
+
+GitLab by default supports [Gravatar](gravatar.com) avatar service.
+Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
+[heavily based on gravatar](http://wiki.libravatar.org/api/).
+
+This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server.
+
+# Configuration
+
+In [gitlab.yml gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122) set
+the configuration options as follows:
+
+## For HTTP
+
+```yml
+ gravatar:
+ enabled: true
+ # gravatar URLs: possible placeholders: %{hash} %{size} %{email}
+ plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+## For HTTPS
+
+```yml
+ gravatar:
+ enabled: true
+ # gravatar URLs: possible placeholders: %{hash} %{size} %{email}
+ ssl_url: "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+## Self-hosted
+
+If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration
+but the important part is to provide the same placeholders so GitLab can parse the URL correctly.
+
+For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is
+
+`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon`
+
+
+## Omnibus-gitlab example
+
+In `/etc/gitlab/gitlab.rb`:
+
+#### For http
+
+```ruby
+gitlab_rails['gravatar_enabled'] = true
+gitlab_rails['gravatar_plain_url'] = "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+#### For https
+
+```ruby
+gitlab_rails['gravatar_enabled'] = true
+gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+
+Run `sudo gitlab-ctl reconfigure` for changes to take effect.
+
+
+## Default URL for missing images
+
+[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service.
+
+In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set.
+For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
new file mode 100644
index 0000000000..6c141d1fb7
--- /dev/null
+++ b/doc/customization/welcome_message.md
@@ -0,0 +1,38 @@
+# Customize the complete sign-in page (GitLab Enterprise Edition only)
+
+Please see [Branded login page](http://doc.gitlab.com/ee/customization/branded_login_page.html)
+
+# Add a welcome message to the sign-in page (GitLab Community Edition)
+
+It is possible to add a markdown-formatted welcome message to your GitLab
+sign-in page. Users of GitLab Enterprise Edition should use the [branded login
+page feature](/ee/customization/branded_login_page.html) instead.
+
+## Omnibus-gitlab example
+
+In `/etc/gitlab/gitlab.rb`:
+
+```ruby
+gitlab_rails['extra_sign_in_text'] = <<'EOS'
+# ACME GitLab
+Welcome to the [ACME](http://www.example.com) GitLab server!
+EOS
+```
+
+Run `sudo gitlab-ctl reconfigure` for changes to take effect.
+
+## Installation from source
+
+In `/home/git/gitlab/config/gitlab.yml`:
+
+```yaml
+# snip
+production:
+ # snip
+ extra:
+ sign_in_text: |
+ # ACME GitLab
+ Welcome to the [ACME](http://www.example.com) GitLab server!
+```
+
+Run `sudo service gitlab reload` for the change to take effect.
diff --git a/doc/development/README.md b/doc/development/README.md
index 67ee828db6..d5d264be19 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -3,3 +3,6 @@
- [Architecture](architecture.md) of GitLab
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Rake tasks](rake_tasks.md) for development
+- [CI setup](ci_setup.md) for testing GitLab
+- [Sidekiq debugging](sidekiq_debugging.md)
+- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 4624d9f60b..541af487bb 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -2,12 +2,44 @@
## Software delivery
-There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://www.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development.
+There are two editions of GitLab: [Enterprise Edition](https://about.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://about.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development.
EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme.
Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
+## Physical office analogy
+
+You can imagine GitLab as a physical office.
+
+**The repositories** are the goods GitLab handling.
+They can be stored in a warehouse.
+This can be either a hard disk, or something more complex, such as a NFS filesystem;
+
+**Nginx** acts like the front-desk.
+Users come to Nginx and request actions to be done by workers in the office;
+
+**The database** is a series of metal file cabinets with information on:
+ - The goods in the warehouse (metadata, issues, merge requests etc);
+ - The users coming to the front desk (permissions)
+
+**Redis** is a communication board with “cubby holes” that can contain tasks for office workers;
+
+**Sidekiq** is a worker that primarily handles sending out emails.
+It takes tasks from the Redis communication board;
+
+**A Unicorn worker** is a worker that handles quick/mundane tasks.
+They work with the communication board (Redis).
+Their job description:
+ - check permissions by checking the user session stored in a Redis “cubby hole”;
+ - make tasks for Sidekiq;
+ - fetch stuff from the warehouse or move things around in there;
+
+**Gitlab-shell** is a third kind of worker that takes orders from a fax machine (SSH) instead of the front desk (HTTP).
+Gitlab-shell communicates with Sidekiq via the “communication board” (Redis), and asks quick questions of the Unicorn workers either directly or via the front desk.
+
+**GitLab Enterprise Edition (the application)** is the collection of processes and business practices that the office is run by.
+
## System Layout
When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git.
@@ -22,7 +54,7 @@ To serve repositories over SSH there's an add-on application called gitlab-shell
![GitLab Diagram Overview](gitlab_diagram_overview.png)
-A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
+A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository. `/home/git/gitlab-satellites` keeps checked out repositories when performing actions such as a merge request, editing files in the web interface, etc.
@@ -38,7 +70,7 @@ To summarize here's the [directory structure of the `git` user home directory](.
ps aux | grep '^git'
-GitLab has several components to operate. As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database. It also uses Apache httpd or nginx to proxypass Unicorn. As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default). Under the GitLab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process).
+GitLab has several components to operate. As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database. It also uses Apache httpd or Nginx to proxypass Unicorn. As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default). Under the GitLab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process).
### Repository access
@@ -114,13 +146,13 @@ nginx
Apache httpd
-- [Explanation of apache logs](http://httpd.apache.org/docs/2.2/logs.html).
+- [Explanation of Apache logs](http://httpd.apache.org/docs/2.2/logs.html).
- `/var/log/apache2/` contains error and output logs (on Ubuntu).
- `/var/log/httpd/` contains error and output logs (on RHEL).
redis
-- `/var/log/redis/redis.log` there are also logrotated logs there.
+- `/var/log/redis/redis.log` there are also log-rotated logs there.
PostgreSQL
diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md
new file mode 100644
index 0000000000..f9b4886818
--- /dev/null
+++ b/doc/development/ci_setup.md
@@ -0,0 +1,46 @@
+# CI setup
+
+This document describes what services we use for testing GitLab and GitLab CI.
+
+We currently use three CI services to test GitLab:
+
+1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
+2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org
+3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
+
+| Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore |
+|---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------|
+| GitLab CE @ MySQL | ✓ | ✓ [Core team can trigger builds](https://gitlab-ce.githost.io/projects/4) | |
+| GitLab CE @ PostgreSQL | | | ✓ [Core team can trigger builds](https://semaphoreapp.com/gitlabhq/gitlabhq/branches/master) |
+| GitLab EE @ MySQL | ✓ | | |
+| GitLab CI @ MySQL | ✓ | | |
+| GitLab CI @ PostgreSQL | | | ✓ |
+| GitLab CI Runner | ✓ | | ✓ |
+| GitLab Shell | ✓ | | ✓ |
+| GitLab Shell | ✓ | | ✓ |
+
+Core team has access to trigger builds if needed for GitLab CE.
+
+We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/build_script_gitlab_ce.md) for testing with GitLab CI.
+
+# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
+
+- Language: Ruby
+- Ruby version: 2.1.2
+- database.yml: pg
+
+Build commands
+
+```bash
+sudo apt-get install cmake libicu-dev -y (Setup)
+bundle install --deployment --path vendor/bundle (Setup)
+cp config/gitlab.yml.example config/gitlab.yml (Setup)
+bundle exec rake db:create (Setup)
+bundle exec rake spinach (Thread #1)
+bundle exec rake spec (thread #2)
+bundle exec rake rubocop (thread #3)
+bundle exec rake brakeman (thread #4)
+bundle exec rake jasmine:ci (thread #5)
+```
+
+Use rubygems mirror.
diff --git a/doc/development/omnibus.md b/doc/development/omnibus.md
new file mode 100644
index 0000000000..0ba354d28a
--- /dev/null
+++ b/doc/development/omnibus.md
@@ -0,0 +1,32 @@
+# What you should know about omnibus packages
+
+Most users install GitLab using our omnibus packages. As a developer it can be
+good to know how the omnibus packages differ from what you have on your laptop
+when you are coding.
+
+## Files are owned by root by default
+
+All the files in the Rails tree (`app/`, `config/` etc.) are owned by 'root' in
+omnibus installations. This makes the installation simpler and it provides
+extra security. The omnibus reconfigure script contains commands that give
+write access to the 'git' user only where needed.
+
+For example, the 'git' user is allowed to write in the `log/` directory, in
+`public/uploads`, and they are allowed to rewrite the `db/schema.rb` file.
+
+In other cases, the reconfigure script tricks GitLab into not trying to write a
+file. For instance, GitLab will generate a `.secret` file if it cannot find one
+and write it to the Rails root. In the omnibus packages, reconfigure writes the
+`.secret` file first, so that GitLab never tries to write it.
+
+## Code, data and logs are in separate directories
+
+The omnibus design separates code (read-only, under `/opt/gitlab`) from data
+(read/write, under `/var/opt/gitlab`) and logs (read/write, under
+`/var/log/gitlab`). To make this happen the reconfigure script sets custom
+paths where it can in GitLab config files, and where there are no path
+settings, it uses symlinks.
+
+For example, `config/gitlab.yml` is treated as data so that file is a symlink.
+The same goes for `public/uploads`. The `log/` directory is replaced by omnibus
+with a symlink to `/var/log/gitlab/gitlab-rails`.
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 6d9ac161e9..53f8095cb1 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -1,6 +1,6 @@
# Rake tasks for developers
-## Setup db with developer seeds:
+## Setup db with developer seeds
Note that if your db user does not have advanced privileges you must create the db manually before running this command.
@@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the
bundle exec rake setup
```
+The `setup` task is a alias for `gitlab:setup`.
+This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and fianlly it calls `db:seed_fu` to seed the database.
+Note: `db:setup` calls `db:seed` but this does nothing.
+
## Run tests
This runs all test suites present in GitLab.
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
index 1f3908f4e2..821027f43f 100644
--- a/doc/development/shell_commands.md
+++ b/doc/development/shell_commands.md
@@ -1,5 +1,8 @@
# Guidelines for shell commands in the GitLab codebase
+This document contains guidelines for working with processes and files in the GitLab codebase.
+These guidelines are meant to make your code more reliable _and_ secure.
+
## References
- [Google Ruby Security Reviewer's Guide](https://code.google.com/p/ruby-security/wiki/Guide)
@@ -22,6 +25,12 @@ FileUtils.mkdir_p "tmp/special/directory"
contents = `cat #{filename}`
# Correct
contents = File.read(filename)
+
+# Sometimes a shell command is just the best solution. The example below has no
+# user input, and is hard to implement correctly in Ruby: delete all files and
+# directories older than 120 minutes under /some/path, but not /some/path
+# itself.
+Gitlab::Popen.popen(%W(find /some/path -not -path /some/path -mmin +120 -delete))
```
This coding style could have prevented CVE-2013-4490.
@@ -99,7 +108,72 @@ In other repositories, such as gitlab-shell you can also use `IO.popen`.
```ruby
# Safe IO.popen example
-logs = IO.popen(%W(git log), chdir: repo_dir).read
+logs = IO.popen(%W(git log), chdir: repo_dir) { |p| p.read }
```
Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error.
+
+## Avoid user input at the start of path strings
+
+Various methods for opening and reading files in Ruby can be used to read the
+standard output of a process instead of a file. The following two commands do
+roughly the same:
+
+```
+`touch /tmp/pawned-by-backticks`
+File.read('|touch /tmp/pawned-by-file-read')
+```
+
+The key is to open a 'file' whose name starts with a `|`.
+Affected methods include Kernel#open, File::read, File::open, IO::open and IO::read.
+
+You can protect against this behavior of 'open' and 'read' by ensuring that an
+attacker cannot control the start of the filename string you are opening. For
+instance, the following is sufficient to protect against accidentally starting
+a shell command with `|`:
+
+```
+# we assume repo_path is not controlled by the attacker (user)
+path = File.join(repo_path, user_input)
+# path cannot start with '|' now.
+File.read(path)
+```
+
+If you have to use user input a relative path, prefix `./` to the path.
+
+Prefixing user-supplied paths also offers extra protection against paths
+starting with `-` (see the discussion about using `--` above).
+
+## Guard against path traversal
+
+Path traversal is a security where the program (GitLab) tries to restrict user
+access to a certain directory on disk, but the user manages to open a file
+outside that directory by taking advantage of the `../` path notation.
+
+```
+# Suppose the user gave us a path and they are trying to trick us
+user_input = '../other-repo.git/other-file'
+
+# We look up the repo path somewhere
+repo_path = 'repositories/user-repo.git'
+
+# The intention of the code below is to open a file under repo_path, but
+# because the user used '..' she can 'break out' into
+# 'repositories/other-repo.git'
+full_path = File.join(repo_path, user_input)
+File.open(full_path) do # Oops!
+```
+
+A good way to protect against this is to compare the full path with its
+'absolute path' according to Ruby's `File.absolute_path`.
+
+```
+full_path = File.join(repo_path, user_input)
+if full_path != File.absolute_path(full_path)
+ raise "Invalid path: #{full_path.inspect}"
+end
+
+File.open(full_path) do # Etc.
+```
+
+A check like this could have avoided CVE-2013-4583.
diff --git a/doc/development/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md
new file mode 100644
index 0000000000..cea11e5f12
--- /dev/null
+++ b/doc/development/sidekiq_debugging.md
@@ -0,0 +1,14 @@
+# Sidekiq debugging
+
+## Log arguments to Sidekiq jobs
+
+If you want to see what arguments are being passed to Sidekiq jobs you can set
+the SIDEKIQ_LOG_ARGUMENTS environment variable.
+
+```
+SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start
+```
+
+It is not recommend to enable this setting in production because some Sidekiq
+jobs (such as sending a password reset email) take secret arguments (for
+example the password reset token).
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
new file mode 100644
index 0000000000..2f01defc11
--- /dev/null
+++ b/doc/development/ui_guide.md
@@ -0,0 +1,12 @@
+# UI Guide for building GitLab
+
+## Best practices for creating new pages in GitLab
+
+TODO: write some best practices when develop GitLab features.
+
+## GitLab UI development kit
+
+We created a page inside GitLab where you can check commonly used html and css elements.
+
+When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples
+you can use during GitLab development.
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
new file mode 100644
index 0000000000..f7d4f3de68
--- /dev/null
+++ b/doc/hooks/custom_hooks.md
@@ -0,0 +1,41 @@
+# Custom Git Hooks
+
+**Note: Custom git hooks must be configured on the filesystem of the GitLab
+server. Only GitLab server administrators will be able to complete these tasks.
+Please explore webhooks as an option if you do not have filesystem access.**
+
+Git natively supports hooks that are executed on different actions.
+Examples of server-side git hooks include pre-receive, post-receive, and update.
+See
+[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks)
+for more information about each hook type.
+
+As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
+administrators can add custom git hooks to any GitLab project.
+
+## Setup
+
+Normally, git hooks are placed in the repository or project's `hooks` directory.
+GitLab creates a symlink from each project's `hooks` directory to the
+gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell
+upgrades. As such, custom hooks are implemented a little differently. Behavior
+is exactly the same once the hook is created, though. Follow these steps to
+set up a custom hook.
+
+1. Pick a project that needs a custom git hook.
+1. On the GitLab server, navigate to the project's repository directory.
+For an installation from source the path is usually
+`/home/git/repositories//.git`. For Omnibus installs the path is
+usually `/var/opt/gitlab/git-data/repositories//.git`.
+1. Create a new directory in this location called `custom_hooks`.
+1. Inside the new `custom_hooks` directory, create a file with a name matching
+the hook type. For a pre-receive hook the file name should be `pre-receive` with
+no extension.
+1. Make the hook file executable and make sure it's owned by git.
+1. Write the code to make the git hook function as expected. Hooks can be
+in any language. Ensure the 'shebang' at the top properly reflects the language
+type. For example, if the script is in Ruby the shebang will probably be
+`#!/usr/bin/env ruby`.
+
+That's it! Assuming the hook code is properly implemented the hook will fire
+as appropriate.
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index 270ad3b0b8..362c492d0a 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -1,4 +1,4 @@
-# Database Mysql
+# Database MySQL
## Note
@@ -12,31 +12,31 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
# Ensure you have MySQL version 5.5.14 or later
mysql --version
- # Pick a database root password (can be anything), type it and press enter
- # Retype the database root password and press enter
+ # Pick a MySQL root password (can be anything), type it and press enter
+ # Retype the MySQL root password and press enter
- # Secure your installation.
+ # Secure your installation
sudo mysql_secure_installation
# Login to MySQL
mysql -u root -p
- # Type the database root password
+ # Type the MySQL root password
# Create a user for GitLab
# do not type the 'mysql>', this is part of the prompt
# change $password in the command below to a real password you pick
mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
- # Ensure you can use the InnoDB engine which is necessary to support long indexes.
+ # Ensure you can use the InnoDB engine which is necessary to support long indexes
# If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
mysql> SET storage_engine=INNODB;
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
- # Grant the GitLab user necessary permissions on the table.
- mysql> GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'git'@'localhost';
+ # Grant the GitLab user necessary permissions on the database
+ mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session
mysql> \q
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 7ac2619259..a61a40ebd1 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -1,24 +1,30 @@
-# Installation
+# Installation from source
+
+## Consider the Omnibus package installation
+
+Since an installation from source is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
## Select Version to Install
-Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below).
+Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the tag (version) of GitLab you would like to install.
+In most cases this should be the highest numbered production tag (without rc in it).
+You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar).
-![capture](http://i.imgur.com/d2AlIVj.png)
+If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
-If the highest number stable branch is unclear please check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version.
-
-## Important notes
+## Important Notes
This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
-This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://www.gitlab.com/downloads/).
+This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/).
This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation).
The following steps have been known to work. Please **use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example many people run into permission problems because they changed the location of directories or run services as the wrong user.
-If you find a bug/error in this guide please **submit a merge request** following the [contributing guide](../../CONTRIBUTING.md).
+If you find a bug/error in this guide please **submit a merge request**
+following the
+[contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
## Overview
@@ -28,6 +34,7 @@ The GitLab installation consists of setting up the following components:
1. Ruby
1. System Users
1. Database
+1. Redis
1. GitLab
1. Nginx
@@ -49,7 +56,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake libkrb5-dev nodejs
Make sure you have the right version of Git installed
@@ -69,14 +76,15 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.0.0.tar.gz | tar xz
- cd git-2.0.0/
+ curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz
+ cd git-2.1.2/
+ ./configure
make prefix=/usr/local all
# Install into /usr/local/bin
sudo make prefix=/usr/local install
- # When editing config/gitlab.yml (Step 5), change the git bin_path to /usr/local/bin/git
+ # When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git
**Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://github.com/gitlabhq/gitlabhq/issues/4866#issuecomment-32726573) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with:
@@ -86,7 +94,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby
-The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system ruby.
+The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby.
Remove the old Ruby 1.8 if present
@@ -95,8 +103,8 @@ Remove the old Ruby 1.8 if present
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl -L --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
- cd ruby-2.1.2
+ curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.6.tar.gz | tar xz
+ cd ruby-2.1.6
./configure --disable-install-rdoc
make
sudo make install
@@ -116,12 +124,13 @@ Create a `git` user for GitLab:
We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). *Note*: because we need to make use of extensions you need at least pgsql 9.1.
# Install the database packages
- sudo apt-get install -y postgresql-9.1 postgresql-client libpq-dev
+ sudo apt-get install -y postgresql postgresql-client libpq-dev
# Login to PostgreSQL
sudo -u postgres psql -d template1
- # Create a user for GitLab.
+ # Create a user for GitLab
+ # Do not type the 'template1=#', this is part of the prompt
template1=# CREATE USER git CREATEDB;
# Create the GitLab production database & grant all privileges on database
@@ -133,7 +142,40 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Try connecting to the new database with the new user
sudo -u git -H psql -d gitlabhq_production
-## 5. GitLab
+ # Quit the database session
+ gitlabhq_production> \q
+
+## 5. Redis
+
+ sudo apt-get install redis-server
+
+ # Configure redis to use sockets
+ sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
+
+ # Disable Redis listening on TCP by setting 'port' to 0
+ sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
+
+ # Enable Redis socket for default Debian / Ubuntu path
+ echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Grant permission to the socket to all members of the redis group
+ echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
+
+ # Create the directory which contains the socket
+ mkdir /var/run/redis
+ chown redis:redis /var/run/redis
+ chmod 755 /var/run/redis
+ # Persist the directory which contains the socket, if applicable
+ if [ -d /etc/tmpfiles.d ]; then
+ echo 'd /var/run/redis 0755 redis redis 10d -' | sudo tee -a /etc/tmpfiles.d/redis.conf
+ fi
+
+ # Activate the changes to redis.conf
+ sudo service redis-server restart
+
+ # Add git to the redis group
+ sudo usermod -aG redis git
+
+## 6. GitLab
# We'll install GitLab into home directory of the user "git"
cd /home/git
@@ -141,32 +183,25 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-2-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-10-stable gitlab
- # Go to gitlab dir
- cd /home/git/gitlab
+**Note:** You can change `7-10-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
-**Note:** You can change `7-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
-
-### Configure it
+### Configure It
+ # Go to GitLab installation folder
cd /home/git/gitlab
# Copy the example GitLab config
sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
- # Make sure to change "localhost" to the fully-qualified domain name of your
- # host serving GitLab where necessary
- #
- # If you want to use https make sure that you set `https` to `true`. See #using-https for all necessary details.
- #
- # If you installed Git from source, change the git bin_path to /usr/local/bin/git
+ # Update GitLab config file, follow the directions at top of file
sudo -u git -H editor config/gitlab.yml
# Make sure GitLab can write to the log/ and tmp/ directories
sudo chown -R git log/
sudo chown -R git tmp/
- sudo chmod -R u+rwX log/
+ sudo chmod -R u+rwX,go-w log/
sudo chmod -R u+rwX tmp/
# Create directory for satellites
@@ -183,8 +218,12 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Copy the example Unicorn config
sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
+ # Find number of cores
+ nproc
+
# Enable cluster mode if you expect to have a high load instance
# Ex. change amount of workers to 3 for 2GB RAM server
+ # Set the number of workers to at least the number of cores
sudo -u git -H editor config/unicorn.rb
# Copy the example Rack attack config
@@ -196,9 +235,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
sudo -u git -H git config --global user.email "example@example.com"
sudo -u git -H git config --global core.autocrlf input
+ # Configure Redis connection settings
+ sudo -u git -H cp config/resque.yml.example config/resque.yml
+
+ # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration
+ sudo -u git -H editor config/resque.yml
+
**Important Note:** Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
-### Configure GitLab DB settings
+**Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps.
+
+### Configure GitLab DB Settings
# PostgreSQL only:
sudo -u git cp config/database.yml.postgresql config/database.yml
@@ -222,35 +269,25 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](http://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2.
- cd /home/git/gitlab
-
# For PostgreSQL (note, the option says "without ... mysql")
sudo -u git -H bundle install --deployment --without development test mysql aws
# Or if you use MySQL (note, the option says "without ... postgres")
sudo -u git -H bundle install --deployment --without development test postgres aws
-### Install GitLab shell
+### Install GitLab Shell
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
- # Go to the GitLab installation folder:
- cd /home/git/gitlab
-
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install[v1.9.7] REDIS_URL=redis://localhost:6379 RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.2] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
- # By default, the gitlab-shell config is generated from your main gitlab config.
- #
- # Note: When using GitLab with HTTPS please change the following:
- # - Provide paths to the certificates under `ca_file` and `ca_path` options.
- # - The `gitlab_url` option must point to the https endpoint of GitLab.
- # - In case you are using self signed certificate set `self_signed_cert` to `true`.
- # See #using-https for all necessary details.
- #
+ # By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
sudo -u git -H editor /home/git/gitlab-shell/config.yml
+**Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps.
+
### Initialize Database and Activate Advanced Features
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
@@ -259,9 +296,13 @@ GitLab Shell is an SSH access and repository management software developed speci
# When done you see 'Administrator account created:'
+**Note:** You can set the Administrator/root password by supplying it in environmental variable `GITLAB_ROOT_PASSWORD` as seen below. If you don't set the password (and it is set to the default one) please wait with exposing GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login you'll be forced to change the default password.
+
+ sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword
+
### Install Init Script
-Download the init script (will be /etc/init.d/gitlab):
+Download the init script (will be `/etc/init.d/gitlab`):
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
@@ -269,13 +310,13 @@ And if you are installing with a non-default folder or user copy and edit the de
sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab
-If you installed GitLab in another directory or as a user other than the default you should change these settings in `/etc/default/gitlab`. Do not edit `/etc/init.d/gitlab as it will be changed on upgrade.
+If you installed GitLab in another directory or as a user other than the default you should change these settings in `/etc/default/gitlab`. Do not edit `/etc/init.d/gitlab` as it will be changed on upgrade.
Make GitLab start on boot:
sudo update-rc.d gitlab defaults 21
-### Set up logrotate
+### Setup Logrotate
sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
@@ -285,7 +326,7 @@ Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
-### Compile assets
+### Compile Assets
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
@@ -295,11 +336,12 @@ Check if GitLab and its environment are configured correctly:
# or
sudo /etc/init.d/gitlab restart
-## 6. Nginx
+## 7. Nginx
**Note:** Nginx is the officially supported web server for GitLab. If you cannot or do not want to use Nginx as your web server, have a look at the [GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/).
### Installation
+
sudo apt-get install -y nginx
### Site Configuration
@@ -315,7 +357,15 @@ Make sure to edit the config file to match your setup:
# domain name of your host serving GitLab.
sudo editor /etc/nginx/sites-available/gitlab
-**Note:** If you want to use https, replace the `gitlab` nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for all necessary details.
+**Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details.
+
+### Test Configuration
+
+Validate your `gitlab` or `gitlab-ssl` Nginx config file with the following command:
+
+ sudo nginx -t
+
+You should receive `syntax is okay` and `test is successful` messages. If you receive errors check your `gitlab` or `gitlab-ssl` Nginx config file for typos, etc. as indicated in the error message given.
### Restart
@@ -335,26 +385,47 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj
### Initial Login
-Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created an admin account for you. You can use it to log in:
+Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in:
root
5iveL!fe
-**Important Note:** Please go over to your profile page and immediately change the password, so nobody can access your GitLab by using this login information later on.
+**Important Note:** On login you'll be prompted to change the password.
**Enjoy!**
+You can use `sudo service gitlab start` and `sudo service gitlab stop` to start and stop GitLab.
+
## Advanced Setup Tips
### Using HTTPS
-To recapitulate what is needed to use GitLab with HTTPS:
+To use GitLab with HTTPS:
-1. In `gitlab.yml` set the `https` option to `true`
-1. In the `config.yml` of gitlab-shell set the relevant options (see the [install GitLab Shell section](#install-gitlab-shell) of this document).
-1. Use the `gitlab-ssl` nginx example config instead of the `gitlab` config.
+1. In `gitlab.yml`:
+ 1. Set the `port` option in section 1 to `443`.
+ 1. Set the `https` option in section 1 to `true`.
+1. In the `config.yml` of gitlab-shell:
+ 1. Set `gitlab_url` option to the HTTPS endpoint of GitLab (e.g. `https://git.example.com`).
+ 1. Set the certificates using either the `ca_file` or `ca_path` option.
+1. Use the `gitlab-ssl` Nginx example config instead of the `gitlab` config.
+ 1. Update `YOUR_SERVER_FQDN`.
+ 1. Update `ssl_certificate` and `ssl_certificate_key`.
+ 1. Review the configuration file and consider applying other security and performance enhancing features.
-### Additional markup styles
+Using a self-signed certificate is discouraged but if you must use it follow the normal directions then:
+
+1. Generate a self-signed SSL certificate:
+
+ ```
+ mkdir -p /etc/nginx/ssl/
+ cd /etc/nginx/ssl/
+ sudo openssl req -newkey rsa:2048 -x509 -nodes -days 3560 -out gitlab.crt -keyout gitlab.key
+ sudo chmod o-r gitlab.key
+ ```
+1. In the `config.yml` of gitlab-shell set `self_signed_cert` to `true`.
+
+### Additional Markup Styles
Apart from the always supported markdown style there are other rich text files that GitLab can display. But you might have to install a dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
@@ -382,44 +453,10 @@ If you are running SSH on a non-standard port, you must change the GitLab user's
You also need to change the corresponding options (e.g. `ssh_user`, `ssh_host`, `admin_uri`) in the `config\gitlab.yml` file.
-### LDAP authentication
+### LDAP Authentication
You can configure LDAP authentication in `config/gitlab.yml`. Please restart GitLab after editing this file.
### Using Custom Omniauth Providers
-GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already ships with a few providers preinstalled (e.g. LDAP, GitHub, Twitter). But sometimes that is not enough and you need to integrate with other authentication solutions. For these cases you can use the Omniauth provider.
-
-#### Steps
-
-These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation.
-
-- Stop GitLab:
-
- sudo service gitlab stop
-
-- Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile):
-
- gem "omniauth-your-auth-provider"
-
-- If you're using MySQL, install the new Omniauth provider gem by running the following command:
-
- sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment
-
-- If you're using PostgreSQL, install the new Omniauth provider gem by running the following command:
-
- sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
-
- > These are the same commands you used in the [Install Gems section](#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
-
-- Start GitLab:
-
- `sudo service gitlab start`
-
-#### Examples
-
-If you have successfully set up a provider that is not shipped with GitLab itself, please let us know.
-
-You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations).
-
-While we can't officially support every possible authentication mechanism out there, we'd like to at least help those with special needs.
+See the [omniauth integration document](../integration/omniauth.md)
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 53f6ccc8c3..7a3216dd2d 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -7,9 +7,9 @@
- Ubuntu
- Debian
- CentOS
-- RedHat Enterprise Linux
-- Scientific Linux
-- Oracle Linux
+- Red Hat Enterprise Linux (please use the CentOS packages and instructions)
+- Scientific Linux (please use the CentOS packages and instructions)
+- Oracle Linux (please use the CentOS packages and instructions)
For the installations options please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
@@ -22,9 +22,9 @@ For the installations options please see [the installation page on the GitLab we
- FreeBSD
On the above unsupported distributions is still possible to install GitLab yourself.
-Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information.
+Please see the [installation from source guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information.
-### Non Unix operating systems such as Windows
+### Non-Unix operating systems such as Windows
GitLab is developed for Unix operating systems.
GitLab does **not** run on Windows and we have no plans of supporting it in the near future.
@@ -34,10 +34,20 @@ Please consider using a virtual machine to run GitLab.
GitLab requires Ruby (MRI) 2.0 or 2.1
You will have to use the standard MRI implementation of Ruby.
-We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab needs several Gems that have native extensions.
+We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab needs several Gems that have native extensions.
## Hardware requirements
+### Storage
+
+The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+
+If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
+
+Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.
+
+If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
+
### CPU
- 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
@@ -50,11 +60,10 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab
### Memory
-- 512MB is the absolute minimum but we do not recommend this amount of memory.
-You will either need to configure 512MB or 1.5GB of swap space.
-With 512MB of swap space you must configure only one unicorn worker.
-With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check).
-If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow.
+You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
+With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
+
+- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise.
- 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users
@@ -65,15 +74,19 @@ If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application.
-### Storage
+## Unicorn Workers
-The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+It's possible to increase the amount of unicorn workers and this will usually help for to reduce the response time of the applications and increase the ability to handle parallel requests.
-If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
+For most instances we recommend using: CPU cores + 1 = unicorn workers.
+So for a machine with 2 cores, 3 unicorn workers is ideal.
-Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.
+For all machines that have 1GB and up we recommend a minimum of three unicorn workers.
+If you have a 512MB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping.
+With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
+If you have a 512MB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping.
-If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
+To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings).
## Database
@@ -85,12 +98,12 @@ Redis stores all user sessions and the background task queue.
The storage requirements for Redis are minimal, about 25kB per user.
Sidekiq processes the background jobs with a multithreaded process.
This process starts with the entire Rails stack (200MB+) but it can grow over time due to memory leaks.
-On a very active server (10.000 active users) the Sidekiq process can use 1GB+ of memory.
+On a very active server (10,000 active users) the Sidekiq process can use 1GB+ of memory.
-## Supported webbrowsers
+## Supported web browsers
- Chrome (Latest stable version)
-- Firefox (Latest released version)
+- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
-- IE 10+
+- IE 10+
\ No newline at end of file
diff --git a/doc/install/structure.md b/doc/install/structure.md
index 67ca189537..5c03f073c1 100644
--- a/doc/install/structure.md
+++ b/doc/install/structure.md
@@ -13,9 +13,9 @@ This is the directory structure you will end up with following the instructions
* `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
* `/home/git/gitlab` - GitLab core software.
* `/home/git/gitlab-satellites` - checked out repositories for merge requests and file editing from web UI. This can be treated as a temporary files directory.
-* `/home/git/gitlab-shell` - Core add-on component of gitlab. Maintains SSH cloning and other functionality.
+* `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality.
* `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)**
-*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of gitlab and `config.yml` of gitlab-shell.*
+*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.*
To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md).
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 357ed03831..286bd34a0b 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -6,14 +6,16 @@ See the documentation below for details on how to configure these services.
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [LDAP](ldap.md) Set up sign in via LDAP
-- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, and Google via OAuth.
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
- [Slack](slack.md) Integrate with the Slack chat service
+- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
+- [Gmail](gitlab_buttons_in_gmail.md) Adds GitLab actions to messages
-Jenkins support is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jenkins.html).
+GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html) and [advanced Jenkins support](http://doc.gitlab.com/ee/integration/jenkins.html).
## Project services
-Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, PivotalTracker and Slack are available in the from of a Project Service.
+Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, Pivotal Tracker, and Slack are available in the form of a Project Service.
You can find these within GitLab in the Services page under Project Settings if you are at least a master on the project.
Project Services are a bit like plugins in that they allow a lot of freedom in adding functionality to GitLab, for example there is also a service that can send an email every time someone pushes new commits.
Because GitLab is open source we can ship with the code and tests for all plugins.
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
new file mode 100644
index 0000000000..d82e1f8b41
--- /dev/null
+++ b/doc/integration/bitbucket.md
@@ -0,0 +1,122 @@
+# Integrate your server with Bitbucket
+
+Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account.
+
+To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket.
+Bitbucket will generate an application ID and secret key for you to use.
+
+1. Sign in to Bitbucket.
+
+1. Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you.
+
+1. Select "OAuth" in the left menu.
+
+1. Select "Add consumer".
+
+1. Provide the required details.
+ - Name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive.
+ - Application description: Fill this in if you wish.
+ - URL: The URL to your GitLab installation. 'https://gitlab.company.com'
+1. Select "Save".
+
+1. You should now see a Key and Secret in the list of OAuth customers.
+ Keep this page open as you continue configuration.
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For instalations from source:
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "bitbucket",
+ "app_id" => "YOUR_KEY",
+ "app_secret" => "YOUR_APP_SECRET",
+ "url" => "https://bitbucket.org/"
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```
+ - { name: 'bitbucket', app_id: 'YOUR_KEY',
+ app_secret: 'YOUR_APP_SECRET' }
+ ```
+
+1. Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7.
+
+1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7.
+
+1. Save the configuration file.
+
+1. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a Bitbucket icon below the regular sign in form.
+Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application.
+If everything goes well the user will be returned to GitLab and will be signed in.
+
+## Bitbucket project import
+
+To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com.
+
+Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key.
+
+### Step 1: Known hosts
+
+To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
+
+1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
+
+ ```sh
+ ssh git@bitbucket.org
+ ```
+
+1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
+
+ ```sh
+ The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
+ RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
+ Are you sure you want to continue connecting (yes/no)?
+ ```
+
+1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+
+1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2:
+
+### Step 2: Public key
+
+To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
+
+If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+
+1. Create a new SSH key:
+
+ ```sh
+ sudo -u git -H ssh-keygen
+ ```
+
+ When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
+ Make sure to use an **empty passphrase**.
+
+2. Restart GitLab to allow it to find the new public key.
+
+You should now see the "Import projects from Bitbucket" option on the New Project page enabled.
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 6245836bef..96755707de 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -1,13 +1,39 @@
# External issue tracker
-GitLab has a great issue tracker but you can also use an external issue tracker such as JIRA or Redmine. This is something that you can turn on per GitLab project. If for example you configure JIRA it provides the following functionality:
+GitLab has a great issue tracker but you can also use an external issue tracker such as Jira, Bugzilla or Redmine. You can configure issue trackers per GitLab project. For instance, if you configure Jira it allows you to do the following:
-- the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index;
-- clicking 'New issue' on the project dashboard creates a new JIRA issue;
-- To reference JIRA issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue.
+- the 'Issues' link on the GitLab project pages takes you to the appropriate Jira issue index;
+- clicking 'New issue' on the project dashboard creates a new Jira issue;
+- To reference Jira issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding Jira issue.
-![jira screenshot](jira-integration-points.png)
+![Jira screenshot](jira-integration-points.png)
-You can configure the integration in the gitlab.yml configuration file.
+GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html).
+
+## Configuration
+
+### Project Service
+
+You can enable an external issue tracker per project. As an example, we will configure `Redmine` for project named gitlab-ci.
+
+Fill in the required details on the page:
+
+![redmine configuration](redmine_configuration.png)
+
+* `description` A name for the issue tracker (to differentiate between instances, for example).
+* `project_url` The URL to the project in Redmine which is being linked to this GitLab project.
+* `issues_url` The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the url. This id is used by GitLab as a placeholder to replace the issue number.
+* `new_issue_url` This is the URL to create a new issue in Redmine for the project linked to this GitLab project.
+
+### Service Template
+
+It is necessary to configure the external issue tracker per project, because project specific details are needed for the integration with GitLab.
+The admin can add a service template that sets a default for each project. This makes it much easier to configure individual projects.
+
+In GitLab Admin section, navigate to `Service Templates` and choose the service template you want to create:
+
+![redmine service template](redmine_service_template.png)
+
+After the template is created, the template details will be pre-filled on the project service page.
Support to add your commits to the Jira ticket automatically is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jira.html).
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 714593d826..b64501c2aa 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -1,6 +1,9 @@
-# GitHub OAuth2 OmniAuth Provider
+# Integrate your server with GitHub
-To enable the GitHub OmniAuth provider you must register your application with GitHub. GitHub will generate a client ID and secret key for you to use.
+Import projects from GitHub and login to your GitLab instance with your GitHub account.
+
+To enable the GitHub OmniAuth provider you must register your application with GitHub.
+GitHub will generate an application ID and secret key for you to use.
1. Sign in to GitHub.
@@ -14,35 +17,63 @@ To enable the GitHub OmniAuth provider you must register your application with G
- Application name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive.
- Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com'
- Application description: Fill this in if you wish.
- - Authorization callback URL: 'https://gitlab.company.com/users/auth/github/callback'
+ - Authorization callback URL: 'https://gitlab.company.com/'
1. Select "Register application".
-1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). Keep this page open as you continue configuration. ![GitHub app](github_app.png)
+1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+ Keep this page open as you continue configuration.
+ ![GitHub app](github_app.png)
1. On your GitLab server, open the configuration file.
+ For omnibus package:
+
```sh
- cd /home/git/gitlab
-
- sudo -u git -H editor config/gitlab.yml
+ sudo editor /etc/gitlab/gitlab.rb
```
-1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details.
+ For instalations from source:
-1. Under `providers:` uncomment (or add) lines that look like the following:
+ ```sh
+ cd /home/git/gitlab
- ```
- - { name: 'github', app_id: 'YOUR APP ID',
- app_secret: 'YOUR APP SECRET',
- args: { scope: 'user:email' } }
+ sudo -u git -H editor config/gitlab.yml
```
-1. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7.
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "github",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "url" => "https://github.com/",
+ "args" => { "scope" => "user:email" }
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```
+ - { name: 'github', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'user:email' } }
+ ```
+
+1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7.
+
+1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7.
1. Save the configuration file.
1. Restart GitLab for the changes to take effect.
-On the sign in page there should now be a GitHub icon below the regular sign in form. Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+On the sign in page there should now be a GitHub icon below the regular sign in form.
+Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application.
+If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/integration/github_app.png b/doc/integration/github_app.png
index c0873b2e20..d890345ced 100644
Binary files a/doc/integration/github_app.png and b/doc/integration/github_app.png differ
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
new file mode 100644
index 0000000000..216f1f11a9
--- /dev/null
+++ b/doc/integration/gitlab.md
@@ -0,0 +1,84 @@
+# Integrate your server with GitLab.com
+
+Import projects from GitLab.com and login to your GitLab instance with your GitLab.com account.
+
+To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com.
+GitLab.com will generate an application ID and secret key for you to use.
+
+1. Sign in to GitLab.com
+
+1. Navigate to your profile settings.
+
+1. Select "Applications" in the left menu.
+
+1. Select "New application".
+
+1. Provide the required details.
+ - Name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive.
+ - Redirect URI:
+
+ ```
+ http://your-gitlab.example.com/import/gitlab/callback
+ http://your-gitlab.example.com/users/auth/gitlab/callback
+ ```
+
+ The first link is required for the importer and second for the authorization.
+
+1. Select "Submit".
+
+1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+ Keep this page open as you continue configuration.
+ ![GitLab app](gitlab_app.png)
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For instalations from source:
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "gitlab",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "args" => { "scope" => "api" }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```
+ - { name: 'gitlab', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'api' } }
+ ```
+
+1. Change 'YOUR_APP_ID' to the Application ID from the GitLab.com application page.
+
+1. Change 'YOUR_APP_SECRET' to the secret from the GitLab.com application page.
+
+1. Save the configuration file.
+
+1. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a GitLab.com icon below the regular sign in form.
+Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application.
+If everything goes well the user will be returned to your GitLab instance and will be signed in.
diff --git a/doc/integration/gitlab_actions.png b/doc/integration/gitlab_actions.png
new file mode 100644
index 0000000000..b08f54d137
Binary files /dev/null and b/doc/integration/gitlab_actions.png differ
diff --git a/doc/integration/gitlab_app.png b/doc/integration/gitlab_app.png
new file mode 100644
index 0000000000..3f9391a821
Binary files /dev/null and b/doc/integration/gitlab_app.png differ
diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md
new file mode 100644
index 0000000000..a9885cef10
--- /dev/null
+++ b/doc/integration/gitlab_buttons_in_gmail.md
@@ -0,0 +1,28 @@
+# GitLab buttons in Gmail
+
+GitLab supports [Google actions in email](https://developers.google.com/gmail/markup/actions/actions-overview).
+
+If correctly setup, emails that require an action will be marked in Gmail.
+
+![gitlab_actions](gitlab_actions.png)
+
+To get this functioning, you need to be registered with Google.
+[See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
+
+To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server.
+
+To check what would be sent to the google email address, run the rake task:
+
+```bash
+bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production
+```
+
+**This will not send the email but give you the output of how the mail will look.**
+
+Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate".
+
+If you receive "No errors detected" message from the tester you can send the email using:
+
+```bash
+bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true
+```
diff --git a/doc/integration/google.md b/doc/integration/google.md
index 7a78aff8ea..e1c14c7c94 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -27,27 +27,50 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
- Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback'
1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](google_app.png)
-1. On your GitLab server, open the configuration file.
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
```sh
- cd /home/git/gitlab
-
- sudo -u git -H editor config/gitlab.yml
+ sudo editor /etc/gitlab/gitlab.rb
```
-1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details.
+ For instalations from source:
-1. Under `providers:` uncomment (or add) lines that look like the following:
+ ```sh
+ cd /home/git/gitlab
- ```
- - { name: 'google_oauth2', app_id: 'YOUR APP ID',
- app_secret: 'YOUR APP SECRET',
- args: { access_type: 'offline', approval_prompt: '' } }
+ sudo -u git -H editor config/gitlab.yml
```
-1. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7.
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "google_oauth2",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "args" => { "access_type" => "offline", "approval_prompt" => '' }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```
+ - { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { access_type: 'offline', approval_prompt: '' } }
+ ```
+
+1. Change 'YOUR_APP_ID' to the client ID from the Google Developer page from step 10.
+
+1. Change 'YOUR_APP_SECRET' to the client secret from the Google Developer page from step 10.
1. Save the configuration file.
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index 62bb957d95..b67f793c59 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -6,6 +6,103 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
+## Configuring GitLab for LDAP integration
+
+To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
+In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server.
+
+Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration.
+The old LDAP integration syntax still works in GitLab 7.4.
+If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab.
+
+```ruby
+# For omnibus packages
+gitlab_rails['ldap_enabled'] = true
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
+main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+
+ host: '_your_ldap_server'
+ port: 389
+ uid: 'sAMAccountName'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
+
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # To maintain tight control over the number of active users on your GitLab installation,
+ # enable this setting to keep new users blocked until they have been cleared by the admin
+ # (default: false).
+ block_auto_created_users: false
+
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+# GitLab EE only: add more LDAP servers
+# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+# so that GitLab can remember which LDAP server a user belongs to.
+# uswest2:
+# label:
+# host:
+# ....
+EOS
+```
+
+If you are getting 'Connection Refused' errors when trying to connect to the LDAP server please double-check the LDAP `port` and `method` settings used by GitLab.
+Common combinations are `method: 'plain'` and `port: 389`, OR `method: 'ssl'` and `port: 636`.
+
+If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
+
+```
+production:
+ # snip...
+ ldap:
+ enabled: false
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+ # snip...
+```
+
## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
@@ -17,3 +114,35 @@ In other words, if an existing GitLab user wants to enable LDAP sign-in for them
GitLab recognizes the following LDAP attributes as email addresses: `mail`, `email` and `userPrincipalName`.
If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `email: foo@example.com`, then the first attribute found wins -- in this case `foo@bar.com`.
+
+## Using an LDAP filter to limit access to your GitLab server
+
+If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter.
+The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515).
+
+```ruby
+# For omnibus packages; new LDAP server syntax
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+EOS
+```
+
+```yaml
+# For installations from source; new LDAP server syntax
+production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+```
+
+Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax:
+
+```
+(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
+```
+
+Please note that GitLab does not support the custom filter syntax used by omniauth-ldap.
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
new file mode 100644
index 0000000000..192c321f71
--- /dev/null
+++ b/doc/integration/oauth_provider.md
@@ -0,0 +1,35 @@
+## GitLab as OAuth2 authentication service provider
+
+This document is about using GitLab as an OAuth authentication service provider to sign into other services.
+If you want to use other OAuth authentication service providers to sign into GitLab please see the [OAuth2 client documentation](../api/oauth2.md)
+
+OAuth2 provides client applications a 'secure delegated access' to server resources on behalf of a resource owner. Or you can allow users to sign in to your application with their GitLab.com account.
+In fact OAuth allows to issue access token to third-party clients by an authorization server,
+with the approval of the resource owner, or end-user.
+Mostly, OAuth2 is using for SSO (Single sign-on). But you can find a lot of different usages for this functionality.
+For example, our feature 'GitLab Importer' is using OAuth protocol to give an access to repositories without sharing user credentials to GitLab.com account.
+Also GitLab.com application can be used for authentication to your GitLab instance if needed [GitLab OmniAuth](gitlab.md).
+
+GitLab has two ways to add new OAuth2 application to an instance, you can add application as regular user and through admin area. So GitLab actually can have an instance-wide and a user-wide applications. There is no defferences between them except the different permission levels.
+
+### Adding application through profile
+Go to your profile section 'Application' and press button 'New Application'
+
+![applications](oauth_provider/user_wide_applications.png)
+
+After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com.
+
+![application_form](oauth_provider/application_form.png)
+
+### Authorized application
+Every application you authorized will be shown in your "Authorized application" sections.
+
+![authorized_application](oauth_provider/authorized_application.png)
+
+At any time you can revoke access just clicking button "Revoke"
+
+### OAuth applications in admin area
+
+If you want to create application that does not belong to certain user you can create it from admin area
+
+![admin_application](oauth_provider/admin_application.png)
\ No newline at end of file
diff --git a/doc/integration/oauth_provider/admin_application.png b/doc/integration/oauth_provider/admin_application.png
new file mode 100644
index 0000000000..a5f34512aa
Binary files /dev/null and b/doc/integration/oauth_provider/admin_application.png differ
diff --git a/doc/integration/oauth_provider/application_form.png b/doc/integration/oauth_provider/application_form.png
new file mode 100644
index 0000000000..ae135db262
Binary files /dev/null and b/doc/integration/oauth_provider/application_form.png differ
diff --git a/doc/integration/oauth_provider/authorized_application.png b/doc/integration/oauth_provider/authorized_application.png
new file mode 100644
index 0000000000..d3ce05be9c
Binary files /dev/null and b/doc/integration/oauth_provider/authorized_application.png differ
diff --git a/doc/integration/oauth_provider/user_wide_applications.png b/doc/integration/oauth_provider/user_wide_applications.png
new file mode 100644
index 0000000000..719e197406
Binary files /dev/null and b/doc/integration/oauth_provider/user_wide_applications.png differ
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 1b0bf9c5f6..24f7b4bb4b 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -1,18 +1,47 @@
# OmniAuth
-GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and other popular services. Configuring
+GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and other popular services.
-OmniAuth does not prevent standard GitLab authentication or LDAP (if configured) from continuing to work. Users can choose to sign in using any of the configured mechanisms.
+Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if configured) from continuing to work. Users can choose to sign in using any of the configured mechanisms.
- [Initial OmniAuth Configuration](#initial-omniauth-configuration)
- [Supported Providers](#supported-providers)
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
+- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
## Initial OmniAuth Configuration
-Before configuring individual OmniAuth providers there are a few global settings that need to be verified.
+Before configuring individual OmniAuth providers there are a few global settings that are in common for all providers that we need to consider.
-1. Open the configuration file.
+- Omniauth needs to be enabled, see details below for example.
+- `allow_single_sign_on` defaults to `false`. If `false` users must be created manually or they will not be able to
+sign in via OmniAuth.
+- `block_auto_created_users` defaults to `true`. If `true` auto created users will be blocked by default and will
+have to be unblocked by an administrator before they are able to sign in.
+- **Note:** If you set `allow_single_sign_on` to `true` and `block_auto_created_users` to `false` please be aware
+that any user on the Internet will be able to successfully sign in to your GitLab without administrative approval.
+
+If you want to change these settings:
+
+* **For omnibus package**
+
+ Open the configuration file:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ and change
+
+ ```
+ gitlab_rails['omniauth_enabled'] = true
+ gitlab_rails['omniauth_allow_single_sign_on'] = false
+ gitlab_rails['block_auto_created_users'] = true
+ ```
+
+* **For installations from source**
+
+ Open the configuration file:
```sh
cd /home/git/gitlab
@@ -20,13 +49,13 @@ Before configuring individual OmniAuth providers there are a few global settings
sudo -u git -H editor config/gitlab.yml
```
-1. Find the section dealing with OmniAuth. The section will look similar to the following.
+ and change the following section
```
- ## OmniAuth settings
+ ## OmniAuth settings
omniauth:
# Allow login via Twitter, Google, etc. using OmniAuth providers
- enabled: false
+ enabled: true
# CAUTION!
# This allows users to login without having a user account first (default: false).
@@ -34,41 +63,17 @@ Before configuring individual OmniAuth providers there are a few global settings
allow_single_sign_on: false
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
-
- ## Auth providers
- # Uncomment the following lines and fill in the data of the auth provider you want to use
- # If your favorite auth provider is not listed you can use others:
- # see https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations
- # The 'app_id' and 'app_secret' parameters are always passed as the first two
- # arguments, followed by optional 'args' which can be either a hash or an array.
- providers:
- # - { name: 'google_oauth2', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET',
- # args: { access_type: 'offline', approval_prompt: '' } }
- # - { name: 'twitter', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET'}
- # - { name: 'github', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET',
- # args: { scope: 'user:email' } }
```
-1. Change `enabled` to `true`.
-
-1. Consider the next two configuration options: `allow_single_sign_on` and `block_auto_created_users`.
-
- - `allow_single_sign_on` defaults to `false`. If `false` users must be created manually or they will not be able to
- sign in via OmniAuth.
- - `block_auto_created_users` defaults to `true`. If `true` auto created users will be blocked by default and will
- have to be unblocked by an administrator before they are able to sign in.
- - **Note:** If you set `allow_single_sign_on` to `true` and `block_auto_created_users` to `false` please be aware
- that any user on the Internet will be able to successfully sign in to your GitLab without administrative approval.
-
-1. Choose one or more of the Supported Providers below to continue configuration.
+Now we can choose one or more of the Supported Providers below to continue configuration.
## Supported Providers
- [GitHub](github.md)
+- [Bitbucket](bitbucket.md)
+- [GitLab.com](gitlab.md)
- [Google](google.md)
+- [Shibboleth](shibboleth.md)
- [Twitter](twitter.md)
## Enable OmniAuth for an Existing User
@@ -82,3 +87,41 @@ Existing users can enable OmniAuth for specific providers after the account is c
1. The user will be redirected to the provider. Once the user authorized GitLab they will be redirected back to GitLab.
The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on.
+
+## Using Custom Omniauth Providers
+
+GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already ships with a few providers preinstalled (e.g. LDAP, GitHub, Twitter). But sometimes that is not enough and you need to integrate with other authentication solutions. For these cases you can use the Omniauth provider.
+
+### Steps
+
+These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation.
+
+- Stop GitLab:
+
+ sudo service gitlab stop
+
+- Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile):
+
+ gem "omniauth-your-auth-provider"
+
+- If you're using MySQL, install the new Omniauth provider gem by running the following command:
+
+ sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment
+
+- If you're using PostgreSQL, install the new Omniauth provider gem by running the following command:
+
+ sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
+
+ > These are the same commands you used in the [Install Gems section](#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
+
+- Start GitLab:
+
+ sudo service gitlab start
+
+### Examples
+
+If you have successfully set up a provider that is not shipped with GitLab itself, please let us know.
+
+You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations).
+
+While we can't officially support every possible authentication mechanism out there, we'd like to at least help those with specific needs.
diff --git a/doc/integration/redmine_configuration.png b/doc/integration/redmine_configuration.png
new file mode 100644
index 0000000000..6b14536322
Binary files /dev/null and b/doc/integration/redmine_configuration.png differ
diff --git a/doc/integration/redmine_service_template.png b/doc/integration/redmine_service_template.png
new file mode 100644
index 0000000000..1159eb5b96
Binary files /dev/null and b/doc/integration/redmine_service_template.png differ
diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md
new file mode 100644
index 0000000000..6258e5f103
--- /dev/null
+++ b/doc/integration/shibboleth.md
@@ -0,0 +1,78 @@
+# Shibboleth OmniAuth Provider
+
+This documentation is for enabling shibboleth with gitlab-omnibus package.
+
+In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however I did not found way to easily configure Nginx that is bundled in gitlab-omnibus package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider.
+
+
+To enable the Shibboleth OmniAuth provider you must:
+
+1. Configure Apache shibboleth module. Installation and configuration of module it self is out of scope of this document.
+Check https://wiki.shibboleth.net/ for more info.
+
+1. You can find Apache config in gitlab-recipes (https://github.com/gitlabhq/gitlab-recipes/blob/master/web-server/apache/gitlab-ssl.conf)
+
+Following changes are needed to enable shibboleth:
+
+protect omniauth-shibboleth callback URL:
+```
+
+ AuthType shibboleth
+ ShibRequestSetting requireSession 1
+ ShibUseHeaders On
+ require valid-user
+
+
+ Alias /shibboleth-sp /usr/share/shibboleth
+
+ Satisfy any
+
+
+
+ SetHandler shib
+
+```
+exclude shibboleth URLs from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibboleth.sso" and "RewriteCond %{REQUEST_URI} !/shibboleth-sp", config should look like this:
+```
+ # Apache equivalent of Nginx try files
+ RewriteEngine on
+ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_URI} !/Shibboleth.sso
+ RewriteCond %{REQUEST_URI} !/shibboleth-sp
+ RewriteRule .* http://127.0.0.1:8080%{REQUEST_URI} [P,QSA]
+ RequestHeader set X_FORWARDED_PROTO 'https'
+```
+
+1. Edit /etc/gitlab/gitlab.rb configuration file, your shibboleth attributes should be in form of "HTTP_ATTRIBUTE" and you should addjust them to your need and environment. Add any other configuration you need.
+
+File should look like this:
+```
+external_url 'https://gitlab.example.com'
+gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
+
+# disable Nginx
+nginx['enable'] = false
+
+gitlab_rails['omniauth_allow_single_sign_on'] = true
+gitlab_rails['omniauth_block_auto_created_users'] = false
+gitlab_rails['omniauth_enabled'] = true
+gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => 'shibboleth',
+ "args" => {
+ "shib_session_id_field" => "HTTP_SHIB_SESSION_ID",
+ "shib_application_id_field" => "HTTP_SHIB_APPLICATION_ID",
+ "uid_field" => 'HTTP_EPPN',
+ "name_field" => 'HTTP_CN',
+ "info_fields" => { "email" => 'HTTP_MAIL'}
+ }
+ }
+]
+
+```
+1. Save changes and reconfigure gitlab:
+```
+sudo gitlab-ctl reconfigure
+```
+
+On the sign in page there should now be a "Sign in with: Shibboleth" icon below the regular sign in form. Click the icon to begin the authentication process. You will be redirected to IdP server (Depends on your Shibboleth module configuration). If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 95cb0c6fae..2fd22c513a 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -4,15 +4,23 @@
To enable Slack integration you must create an Incoming WebHooks integration on Slack;
-1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services)
-1. Click on the Integrations menu at the top of the page.
-1. Add a new Integration.
-1. Pick Incoming WebHooks
-1. Choose the channel name you want to send notifications to, in the Settings section
-1. Add Integrations.
- - Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings".
+1. [Sign in to Slack](https://slack.com/signin)
+
+1. Select **Configure Integrations** from the dropdown next to your team name.
+
+1. Select the **All Services** tab
+
+1. Click **Add** next to Incoming Webhooks
+
+1. Pick Incoming WebHooks
+
+1. Choose the channel name you want to send notifications to
+
+1. Click **Add Incoming WebHooks Integration**Add Integrations.
+ - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
+
+1. Copy the **Webhook URL**, we'll need this later for GitLab.
-Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long.
## On GitLab
@@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this
1. Fill in your Slack details
- - Mark as active it
- - Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain)
- - Type in the token you got from Slack
- - Type in the channel name you want to use (eg. #announcements)
+ - Mark it as active
+ - Paste in the webhook URL you got from Slack
Have fun :)
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index d1b52927d3..fe9091ad9a 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -13,7 +13,7 @@ To enable the Twitter OmniAuth provider you must register your application with
something else descriptive.
- Description: Create a description.
- Website: The URL to your GitLab installation. 'https://gitlab.example.com'
- - Callback URL: 'https://gitlab.example.com/users/auth/github/callback'
+ - Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- Agree to the "Rules of the Road."
![Twitter App Details](twitter_app_details.png)
@@ -33,25 +33,46 @@ To enable the Twitter OmniAuth provider you must register your application with
1. On your GitLab server, open the configuration file.
+ For omnibus package:
+
```sh
- cd /home/git/gitlab
-
- sudo -u git -H editor config/gitlab.yml
+ sudo editor /etc/gitlab/gitlab.rb
```
-1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration)
-for more details.
+ For instalations from source:
-1. Under `providers:` uncomment (or add) lines that look like the following:
+ ```sh
+ cd /home/git/gitlab
- ```
- - { name: 'twitter', app_id: 'YOUR APP ID',
- app_secret: 'YOUR APP SECRET' }
+ sudo -u git -H editor config/gitlab.yml
```
-1. Change 'YOUR APP ID' to the API key from Twitter page in step 11.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Change 'YOUR APP SECRET' to the API secret from the Twitter page in step 11.
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "twitter",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET"
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```
+ - { name: 'twitter', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ ```
+
+1. Change 'YOUR_APP_ID' to the API key from Twitter page in step 11.
+
+1. Change 'YOUR_APP_SECRET' to the API secret from the Twitter page in step 11.
1. Save the configuration file.
diff --git a/doc/logs/logs.md b/doc/logs/logs.md
new file mode 100644
index 0000000000..ec0109a426
--- /dev/null
+++ b/doc/logs/logs.md
@@ -0,0 +1,102 @@
+## Log system
+GitLab has advanced log system so everything is logging and you can analize your instance using various system log files.
+In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://doc.gitlab.com/ee/administration/audit_events.html)
+
+System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files.
+
+#### production.log
+This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/logs/production.log` for installations from the source.
+
+This file contains information about all performed requests. You can see url and type of request, IP address and what exactly parts of code were involved to service this particular request. Also you can see all SQL request that have been performed and how much time it took.
+This task is more useful for GitLab contributors and developers. Use part of this log file when you are going to report bug.
+
+```
+Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200
+Processing by Projects::TreeController#show as HTML
+ Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"}
+
+ ... [CUT OUT]
+
+ amespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1[0m [["id", 26]]
+ [1m[35mCACHE (0.0ms)[0m SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members"."type" IN ('ProjectMember') AND "members"."source_id" = $1 AND "members"."source_type" = $2 AND "members"."user_id" = 1 ORDER BY "members"."created_at" DESC, "members"."id" DESC LIMIT 1 [["source_id", 18], ["source_type", "Project"]]
+ [1m[36mCACHE (0.0ms)[0m [1mSELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members".
+ [1m[36m (1.4ms)[0m [1mSELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."target_project_id" = $1 AND ("merge_requests"."state" IN ('opened','reopened'))[0m [["target_project_id", 18]]
+ Rendered layouts/nav/_project.html.haml (28.0ms)
+ Rendered layouts/_collapse_button.html.haml (0.2ms)
+ Rendered layouts/_flash.html.haml (0.1ms)
+ Rendered layouts/_page.html.haml (32.9ms)
+Completed 200 OK in 166ms (Views: 117.4ms | ActiveRecord: 27.2ms)
+```
+In this example we can see that server processed HTTP request with url `/gitlabhq/yaml_db/tree/master` from IP 168.111.56.1 at 2015-02-12 19:34:53 +0200. Also we can see that request was processed by Projects::TreeController.
+
+#### application.log
+This file lives in `/var/log/gitlab/gitlab-rails/application.log` for omnibus package or in `/home/git/gitlab/logs/application.log` for installations from the source.
+
+This log file helps you discover events happening in your instance such as user creation, project removing and so on.
+
+```
+October 06, 2014 11:56: User "Administrator" (admin@example.com) was created
+October 06, 2014 11:56: Documentcloud created a new project "Documentcloud / Underscore"
+October 06, 2014 11:56: Gitlab Org created a new project "Gitlab Org / Gitlab Ce"
+October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was removed
+October 07, 2014 11:25: Project "project133" was removed
+```
+#### githost.log
+This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for omnibus package or in `/home/git/gitlab/logs/githost.log` for installations from the source.
+
+The GitLab has to interact with git repositories but in some rare cases something can go wrong and in this case you will know what exactly happened. This log file contains all failed requests from GitLab to git repository. In majority of cases this file will be useful for developers only.
+```
+December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict
+
+error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
+```
+
+#### satellites.log
+This file lives in `/var/log/gitlab/gitlab-rails/satellites.log` for omnibus package or in `/home/git/gitlab/logs/satellites.log` for installations from the source.
+
+In some cases GitLab should perform write actions to git repository, for example when it is needed to merge the merge request or edit a file with online editor. If something went wrong you can look into this file to find out what exactly happened.
+```
+October 07, 2014 11:36: Failed to create satellite for Chesley Weimann III / project1817
+October 07, 2014 11:36: PID: 1872: git clone /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/conrad6841/gitlabhq
+October 07, 2014 11:36: PID: 1872: -> fatal: repository '/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git' does not exist
+```
+
+#### sidekiq.log
+This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/logs/sidekiq.log` for installations from the source.
+
+GitLab uses background jobs for processing tasks which can take a long time. All information about processing these jobs are writing down to this file.
+```
+2014-06-10T07:55:20Z 2037 TID-tm504 ERROR: /opt/bitnami/apps/discourse/htdocs/vendor/bundle/ruby/1.9.1/gems/redis-3.0.7/lib/redis/client.rb:228:in `read'
+2014-06-10T18:18:26Z 14299 TID-55uqo INFO: Booting Sidekiq 3.0.0 with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"}
+```
+
+#### gitlab-shell.log
+This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for omnibus package or in `/home/git/gitlab-shell/logs/sidekiq.log` for installations from the source.
+
+gitlab-shell is using by Gitlab for executing git commands and provide ssh access to git repositories.
+
+```
+I, [2015-02-13T06:17:00.671315 #9291] INFO -- : Adding project root/example.git at .
+I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory and simlinking global hooks directory for /var/opt/gitlab/git-data/repositories/root/example.git.
+```
+
+#### unicorn_stderr.log
+This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for omnibus package or in `/home/git/gitlab/logs/unicorn_stderr.log` for installations from the source.
+
+Unicorn is a high-performance forking Web server which is used for serving GitLab application. You can look at this log, for example, if your application does not respond. This log cantains all information about state of unicorn processes at any given time.
+
+```
+I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list
+I, [2015-02-13T06:14:56.931002 #9047] INFO -- : listening on addr=127.0.0.1:8080 fd=12
+I, [2015-02-13T06:14:56.931381 #9047] INFO -- : listening on addr=/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket fd=13
+I, [2015-02-13T06:14:56.936638 #9047] INFO -- : master process ready
+I, [2015-02-13T06:14:56.946504 #9092] INFO -- : worker=0 spawned pid=9092
+I, [2015-02-13T06:14:56.946943 #9092] INFO -- : worker=0 ready
+I, [2015-02-13T06:14:56.947892 #9094] INFO -- : worker=1 spawned pid=9094
+I, [2015-02-13T06:14:56.948181 #9094] INFO -- : worker=1 ready
+W, [2015-02-13T07:16:01.312916 #9094] WARN -- : #: worker (pid: 9094) exceeds memory limit (320626688 bytes > 247066940 bytes)
+W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 9094) alive: 3621 sec (trial 1)
+I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped # worker=1
+I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379
+I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
+```
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 5627fd0659..1d5fd4c8b0 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -6,10 +6,11 @@
* [Newlines](#newlines)
* [Multiple underscores in words](#multiple-underscores-in-words)
-* [URL autolinking](#url-autolinking)
+* [URL auto-linking](#url-auto-linking)
* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
* [Emoji](#emoji)
* [Special GitLab references](#special-gitlab-references)
+* [Task lists](#task-lists)
**[Standard Markdown](#standard-markdown)**
@@ -39,20 +40,21 @@ You can use GFM in
- milestones
- wiki pages
-You can also use other rich text files in GitLab. You might have to install a depency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
+You can also use other rich text files in GitLab. You might have to install a dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
## Newlines
GFM honors the markdown specification in how [paragraphs and line breaks are handled](http://daringfireball.net/projects/markdown/syntax#p).
-A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.:
+A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.
+Line-breaks, or softreturns, are rendered if you end a line with two or more spaces
- Roses are red
+ Roses are red [followed by two or more spaces]
Violets are blue
Sugar is sweet
-Roses are red
+Roses are red
Violets are blue
Sugar is sweet
@@ -67,7 +69,7 @@ It is not reasonable to italicize just _part_ of a word, especially when you're
perform_complicated_task
do_this_and_do_that_and_another_thing
-## URL autolinking
+## URL auto-linking
GFM will autolink standard URLs you copy and paste into your text. So if you want to link to a URL (instead of a textural link), you can simply put the URL in verbatim and it will be turned into a link to that URL.
@@ -139,25 +141,25 @@ But let's throw in a tag.
## Emoji
- Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you:
+ Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
- :high_voltage_sign: You can use emoji anywhere GFM is supported. :victory_hand:
+ :zap: You can use emoji anywhere GFM is supported. :v:
- You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches. And if someone improves your really :snail: code, send them some :cake:. People will :heart: you for that.
+ You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
- If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
+ If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
- Consult the [Emoji Cheat Sheet](https://www.dropbox.com/s/b9xaqb977s6d8w1/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup:
+ Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
-Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you:
+Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
-:high_voltage_sign: You can use emoji anywhere GFM is supported. :victory_hand:
+:zap: You can use emoji anywhere GFM is supported. :v:
-You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches. And if someone improves your really :snail: code, send them some :cake:. People will :heart: you for that.
+You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
-If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
+If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
-Consult the [Emoji Cheat Sheet](https://www.dropbox.com/s/b9xaqb977s6d8w1/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup:
+Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
## Special GitLab References
@@ -169,7 +171,7 @@ GFM will turn that reference into a link so you can navigate between them easily
GFM will recognize the following:
-- @foo : for team members
+- @foo : for specific team members or groups
- @all : for the whole team
- #123 : for issues
- !123 : for merge requests
@@ -177,6 +179,24 @@ GFM will recognize the following:
- 1234567 : for commits
- \[file\](path/to/file) : for file references
+GFM also recognizes references to commits, issues, and merge requests in other projects:
+
+- namespace/project#123 : for issues
+- namespace/project!123 : for merge requests
+- namespace/project@1234567 : for commits
+
+## Task Lists
+
+You can add task lists to merge request and issue descriptions to keep track of to-do items. To create a task, add an unordered list to the description in an issue or merge request, formatted like so:
+
+```no-highlight
+* [x] Completed task
+* [ ] Unfinished task
+ * [x] Nested task
+```
+
+Task lists can only be created in descriptions, not in titles or comments. Task item state can be managed by editing the description's Markdown or by clicking the rendered checkboxes.
+
# Standard Markdown
## Headers
@@ -231,17 +251,17 @@ The IDs are generated from the content of the header according to the following
For example:
```
-###### ..Ab_c-d. e [anchor](url) ![alt text](url)..
+###### ..Ab_c-d. e [anchor](URL) ![alt text](URL)..
```
which renders as:
-###### ..Ab_c-d. e [anchor](url) ![alt text](url)..
+###### ..Ab_c-d. e [anchor](URL) ![alt text](URL)..
will first be converted by step 1) into a string like:
```
-..Ab_c-d. e <a href="url">anchor</a> <img src="url" alt="alt text"/>..
+..Ab_c-d. e <a href="URL">anchor</a> <img src="URL" alt="alt text"/>..
```
After removing the tags in step 2) we get:
@@ -258,8 +278,8 @@ ab_c-d-e-anchor
Note in particular how:
-- for markdown anchors `[text](url)`, only the `text` is used
-- markdown images `![alt](url)` are completely ignored
+- for markdown anchors `[text](URL)`, only the `text` is used
+- markdown images `![alt](URL)` are completely ignored
## Emphasis
@@ -401,6 +421,8 @@ Quote break.
You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
+See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows the `class`, `id`, and `style` attributes.
+
```no-highlight
Definition list
@@ -464,6 +486,10 @@ This line is separated from the one above by two newlines, so it will be a *sepa
This line is also a separate paragraph, but...
This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+
+This line is also a separate paragraph, and...
+This line is on its own line, because the previous line ends with two
+spaces.
```
Here's a line for us to start with.
@@ -473,6 +499,10 @@ This line is separated from the one above by two newlines, so it will be a *sepa
This line is also begins a separate paragraph, but...
This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+This line is also a separate paragraph, and...
+This line is on its own line, because the previous line ends with two
+spaces.
+
## Tables
Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them.
@@ -491,6 +521,10 @@ Code above produces next output:
| cell 1 | cell 2 |
| cell 3 | cell 4 |
+**Note**
+
+The row of dashes between the table header and body must have at least three dashes in each column.
+
## References
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
diff --git a/doc/operations/README.md b/doc/operations/README.md
new file mode 100644
index 0000000000..f1456c6c8e
--- /dev/null
+++ b/doc/operations/README.md
@@ -0,0 +1,4 @@
+# GitLab operations
+
+- [Sidekiq MemoryKiller](sidekiq_memory_killer.md)
+- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md)
diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md
new file mode 100644
index 0000000000..93521e976d
--- /dev/null
+++ b/doc/operations/cleaning_up_redis_sessions.md
@@ -0,0 +1,52 @@
+# Cleaning up stale Redis sessions
+
+Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis.
+Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If
+you have been running a large GitLab server (thousands of users) since before
+GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis
+database after you upgrade to GitLab 7.3. You can also perform a cleanup while
+still running GitLab 7.2 or older, but in that case new stale sessions will
+start building up again after you clean up.
+
+In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte
+hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with
+GitLab 7.3.0, the keys are
+prefixed with 'session:gitlab:', so they would look like
+'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to
+remove the keys in the old format.
+
+First we define a shell function with the proper Redis connection details.
+
+```
+rcli() {
+ # This example works for Omnibus installations of GitLab 7.3 or newer. For an
+ # installation from source you will have to change the socket path and the
+ # path to redis-cli.
+ sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@"
+}
+
+# test the new shell function; the response should be PONG
+rcli ping
+```
+
+Now we do a search to see if there are any session keys in the old format for
+us to clean up.
+
+```
+# returns the number of old-format session keys in Redis
+rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l
+```
+
+If the number is larger than zero, you can proceed to expire the keys from
+Redis. If the number is zero there is nothing to clean up.
+
+```
+# Tell Redis to expire each matched key after 600 seconds.
+rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli
+# This will print '(integer) 1' for each key that gets expired.
+```
+
+Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis
+background save interval) your Redis database will be compacted. If you are
+still using GitLab 7.2, users who are not clicking around in GitLab during the
+10 minute expiry window will be signed out of GitLab.
diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md
new file mode 100644
index 0000000000..867b01b0d5
--- /dev/null
+++ b/doc/operations/sidekiq_memory_killer.md
@@ -0,0 +1,38 @@
+# Sidekiq MemoryKiller
+
+The GitLab Rails application code suffers from memory leaks. For web requests
+this problem is made manageable using
+[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which
+restarts Unicorn worker processes in between requests when needed. The Sidekiq
+MemoryKiller applies the same approach to the Sidekiq processes used by GitLab
+to process background jobs.
+
+Unlike unicorn-worker-killer, which is enabled by default for all GitLab
+installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default
+_only_ for Omnibus packages. The reason for this is that the MemoryKiller
+relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab
+installations from source do not all use Runit or an equivalent.
+
+With the default settings, the MemoryKiller will cause a Sidekiq restart no
+more often than once every 15 minutes, with the restart causing about one
+minute of delay for incoming background jobs.
+
+## Configuring the MemoryKiller
+
+The MemoryKiller is controlled using environment variables.
+
+- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is
+ greater than 0, then after each Sidekiq job, the MemoryKiller will check the
+ RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq
+ process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a
+ delayed shutdown is triggered. The default value for Omnibus packages is set
+ [in the omnibus-gitlab
+ repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
+- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When
+ a shutdown is triggered, the Sidekiq process will keep working normally for
+ another 15 minutes.
+- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace
+ time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs.
+ Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells
+ Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
+ restart Sidekiq.
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index db22b7dbe5..8cfa7f9c87 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -8,7 +8,6 @@ If a user is a GitLab administrator they receive all permissions.
## Project
-
| Action | Guest | Reporter | Developer | Master | Owner |
|---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
@@ -19,6 +18,7 @@ If a user is a GitLab administrator they receive all permissions.
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
@@ -27,17 +27,25 @@ If a user is a GitLab administrator they receive all permissions.
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
-| Enable/Disable branch protection | | | | ✓ | ✓ |
+| Enable/disable branch protection | | | | ✓ | ✓ |
+| Turn on/off prot. branch push for devs| | | | ✓ | ✓ |
| Rewrite/remove git tags | | | | ✓ | ✓ |
| Edit project | | | | ✓ | ✓ |
-| Add Deploy Keys to project | | | | ✓ | ✓ |
-| Configure Project Hooks | | | | ✓ | ✓ |
+| Add deploy keys to project | | | | ✓ | ✓ |
+| Configure project hooks | | | | ✓ | ✓ |
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
+| Force push to protected branches | | | | | |
+| Remove protected branches | | | | | |
## Group
+In order for a group to appear as public and be browsable, it must contain at
+least one public project.
+
+Any user can remove themselves from a group, unless they are the last Owner of the group.
+
| Action | Guest | Reporter | Developer | Master | Owner |
|-------------------------|-------|----------|-----------|--------|-------|
| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
@@ -45,5 +53,3 @@ If a user is a GitLab administrator they receive all permissions.
| Create project in group | | | | ✓ | ✓ |
| Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ |
-
-Any user can remove himself from a group, unless he is the last Owner of the group.
diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md
new file mode 100644
index 0000000000..51668128c6
--- /dev/null
+++ b/doc/project_services/bamboo.md
@@ -0,0 +1,60 @@
+# Atlassian Bamboo CI Service
+
+GitLab provides integration with Atlassian Bamboo for continuous integration.
+When configured, pushes to a project will trigger a build in Bamboo automatically.
+Merge requests will also display CI status showing whether the build is pending,
+failed, or completed successfully. It also provides a link to the Bamboo build
+page for more information.
+
+Bamboo doesn't quite provide the same features as a traditional build system when
+it comes to accepting webhooks and commit data. There are a few things that
+need to be configured in a Bamboo build plan before GitLab can integrate.
+
+## Setup
+
+### Complete these steps in Bamboo:
+
+1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions'
+dropdown.
+1. Select the 'Triggers' tab.
+1. Click 'Add trigger'.
+1. Enter a description such as 'GitLab trigger'
+1. Choose 'Repository triggers the build when changes are committed'
+1. Check one or more repositories checkboxes
+1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a
+whitelist of IP addresses that are allowed to trigger Bamboo builds.
+1. Save the trigger.
+1. In the left pane, select a build stage. If you have multiple build stages
+you want to select the last stage that contains the git checkout task.
+1. Select the 'Miscellaneous' tab.
+1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}'
+in the 'Labels' box.
+1. Save
+
+Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo
+service in GitLab
+
+### Complete these steps in GitLab:
+
+1. Navigate to the project you want to configure to trigger builds.
+1. Select 'Settings' in the top navigation.
+1. Select 'Services' in the left navigation.
+1. Click 'Atlassian Bamboo CI'
+1. Select the 'Active' checkbox.
+1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com'
+1. Enter the build key from your Bamboo build plan. Build keys are a short,
+all capital letter, identifier that is unique. It will be something like PR-BLD
+1. If necessary, enter username and password for a Bamboo user that has
+access to trigger the build plan. Leave these fields blank if you do not require
+authentication.
+1. Save or optionally click 'Test Settings'. Please note that 'Test Settings'
+will actually trigger a build in Bamboo.
+
+## Troubleshooting
+
+If builds are not triggered, these are a couple of things to keep in mind.
+
+1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger
+IP addresses'.
+1. Remember that GitLab only triggers builds on push events. A commit via the
+web interface will not trigger CI currently.
diff --git a/doc/project_services/hipchat.md b/doc/project_services/hipchat.md
new file mode 100644
index 0000000000..021a93a288
--- /dev/null
+++ b/doc/project_services/hipchat.md
@@ -0,0 +1,54 @@
+# Atlassian HipChat
+
+GitLab provides a way to send HipChat notifications upon a number of events,
+such as when a user pushes code, creates a branch or tag, adds a comment, and
+creates a merge request.
+
+## Setup
+
+GitLab requires the use of a HipChat v2 API token to work. v1 tokens are
+not supported at this time. Note the differences between v1 and v2 tokens:
+
+HipChat v1 API (legacy) supports "API Auth Tokens" in the Group API menu. A v1
+token is allowed to send messages to *any* room.
+
+HipChat v2 API has tokens that are can be created using the Integrations tab
+in the Group or Room admin page. By design, these are lightweight tokens that
+allow GitLab to send messages only to *one* room.
+
+### Complete these steps in HipChat:
+
+1. Go to: https://admin.hipchat.com/admin
+1. Click on "Group Admin" -> "Integrations".
+1. Find "Build Your Own!" and click "Create".
+1. Select the desired room, name the integration "GitLab", and click "Create".
+1. In the "Send messages to this room by posting this URL" column, you should
+see a URL in the format:
+
+```
+ https://api.hipchat.com/v2/room//notification?auth_token=
+```
+
+HipChat is now ready to accept messages from GitLab. Next, set up the HipChat
+service in GitLab.
+
+### Complete these steps in GitLab:
+
+1. Navigate to the project you want to configure for notifications.
+1. Select "Settings" in the top navigation.
+1. Select "Services" in the left navigation.
+1. Click "HipChat".
+1. Select the "Active" checkbox.
+1. Insert the `token` field from the URL into the `Token` field on the Web page.
+1. Insert the `room` field from the URL into the `Room` field on the Web page.
+1. Save or optionally click "Test Settings".
+
+## Troubleshooting
+
+If you do not see notifications, make sure you are using a HipChat v2 API
+token, not a v1 token.
+
+Note that the v2 token is tied to a specific room. If you want to be able to
+specify arbitrary rooms, you can create an API token for a specific user in
+HipChat under "Account settings" and "API access". Use the `XXX` value under
+`auth_token=XXX`.
diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md
new file mode 100644
index 0000000000..780a45bca2
--- /dev/null
+++ b/doc/project_services/irker.md
@@ -0,0 +1,46 @@
+# Irker IRC Gateway
+
+GitLab provides a way to push update messages to an Irker server. When
+configured, pushes to a project will trigger the service to send data directly
+to the Irker server.
+
+See the project homepage for further info: http://www.catb.org/esr/irker/
+
+## Needed setup
+
+You will first need an Irker daemon. You can download the Irker code from its
+gitorious repository on https://gitorious.org/irker: `git clone
+git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can
+run the python script named `irkerd`. This script is the gateway script, it acts
+both as an IRC client, for sending messages to an IRC server obviously, and as a
+TCP server, for receiving messages from the GitLab service.
+
+If the Irker server runs on the same machine, you are done. If not, you will
+need to follow the firsts steps of the next section.
+
+## Optional setup
+
+In the `app/models/project_services/irker_service.rb` file, you can modify some
+options in the `initialize_settings` method:
+- **server_ip** (defaults to `localhost`): the server IP address where the
+`irkerd` daemon runs;
+- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon;
+- **max_channels** (defaults to `3`): the maximum number of recipients the
+client is authorized to join, per project;
+- **default_irc_uri** (no default) : if this option is set, it has to be in the
+format `irc[s]://domain.name` and will be prepend to each and every channel
+provided by the user which is not a full URI.
+
+If the Irker server and the GitLab application do not run on the same host, you
+will **need** to setup at least the **server_ip** option.
+
+## Note on Irker recipients
+
+Irker accepts channel names of the form `chan` and `#chan`, both for the
+`#chan` channel. If you want to send messages in query, you will need to add
+`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter
+case, `Aorimn` is treated as a nick and no more as a channel name.
+
+Irker can also join password-protected channels. Users need to append
+`?key=thesecretpassword` to the chan name.
+
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
new file mode 100644
index 0000000000..03937d2072
--- /dev/null
+++ b/doc/project_services/project_services.md
@@ -0,0 +1,20 @@
+# Project Services
+
+__Project integrations with external services for continuous integration and more.__
+
+## Services
+
+- Assembla
+- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continuous integration.
+- Build box
+- Campfire
+- Emails on push
+- Flowdock
+- Gemnasium
+- GitLab CI
+- [HipChat](hipchat.md) An Atlassian product for private group chat and instant messaging.
+- [Irker](irker.md) An IRC gateway to receive messages on repository updates.
+- Pivotal Tracker
+- Pushover
+- Slack
+- TeamCity
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 4712c38702..bd439f7c6f 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -41,4 +41,4 @@ When visiting the public page of an user, you will only see listed projects whic
## Restricting the use of public or internal projects
-In [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/dbd88d453b8e6c78a423fa7e692004b1db6ea069/config/gitlab.yml.example#L64) you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident.
+In the Admin area under Settings you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident. The restricted visibility settings do not apply to admin users.
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 9e2f697bca..770b7a70fe 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -1,5 +1,8 @@
+# Rake tasks
+
- [Backup restore](backup_restore.md)
- [Cleanup](cleanup.md)
+- [Features](features.md)
- [Maintenance](maintenance.md) and self-checks
- [User management](user_management.md)
- [Web hooks](web_hooks.md)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 8200a8cf63..2e41fad89e 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -6,15 +6,22 @@
A backup creates an archive file that contains the database, all repositories and all attachments.
This archive will be saved in backup_path (see `config/gitlab.yml`).
-
The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup.
+You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
```
-# omnibus-gitlab
+# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create
-# installation from source or cookbook
-bundle exec rake gitlab:backup:create RAILS_ENV=production
+# if you've installed GitLab from source
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+Also you can choose what should be backed up by adding environment variable SKIP. Available options: db,
+uploads (attachments), repositories. Use a comma to specify several options at the same time.
+
+```
+sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
```
Example output:
@@ -46,20 +53,108 @@ Deleting tmp directories...[DONE]
Deleting old backups... [SKIPPING]
```
+## Upload backups to remote (cloud) storage
+
+Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates.
+It uses the [Fog library](http://fog.io/) to perform the upload.
+In the example below we use Amazon S3 for storage.
+But Fog also lets you use [other storage providers](http://fog.io/storage/).
+
+For omnibus packages:
+
+```ruby
+gitlab_rails['backup_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-west-1',
+ 'aws_access_key_id' => 'AKIAKIAKI',
+ 'aws_secret_access_key' => 'secret123'
+}
+gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
+```
+
+For installations from source:
+
+```yaml
+ backup:
+ # snip
+ upload:
+ # Fog storage connection settings, see http://fog.io/storage/ .
+ connection:
+ provider: AWS
+ region: eu-west-1
+ aws_access_key_id: AKIAKIAKI
+ aws_secret_access_key: 'secret123'
+ # The remote 'directory' to store your backups. For S3, this would be the bucket name.
+ remote_directory: 'my.s3.bucket'
+```
+
+If you are uploading your backups to S3 you will probably want to create a new
+IAM user with restricted access rights. To give the upload user access only for
+uploading backups create the following IAM profile, replacing `my.s3.bucket`
+with the name of your bucket:
+
+```json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "Stmt1412062044000",
+ "Effect": "Allow",
+ "Action": [
+ "s3:AbortMultipartUpload",
+ "s3:GetBucketAcl",
+ "s3:GetBucketLocation",
+ "s3:GetObject",
+ "s3:GetObjectAcl",
+ "s3:ListBucketMultipartUploads",
+ "s3:PutObject",
+ "s3:PutObjectAcl"
+ ],
+ "Resource": [
+ "arn:aws:s3:::my.s3.bucket/*"
+ ]
+ },
+ {
+ "Sid": "Stmt1412062097000",
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetBucketLocation",
+ "s3:ListAllMyBuckets"
+ ],
+ "Resource": [
+ "*"
+ ]
+ },
+ {
+ "Sid": "Stmt1412062128000",
+ "Effect": "Allow",
+ "Action": [
+ "s3:ListBucket"
+ ],
+ "Resource": [
+ "arn:aws:s3:::my.s3.bucket"
+ ]
+ }
+ ]
+}
+```
+
## Storing configuration files
Please be informed that a backup does not store your configuration files.
-If you use Omnibus-GitLab please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
+If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef.
-If you have a manual installation please consider backing up your gitlab.yml file and any SSL keys and certificates.
+If you have an installation from source, please consider backing up your `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
## Restore a previously created backup
+You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
+
```
-# omnibus-gitlab
+# Omnibus package installation
sudo gitlab-rake gitlab:backup:restore
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
@@ -102,8 +197,9 @@ Deleting tmp directories...[DONE]
## Configure cron to make daily backups
-For omnibus-gitlab, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup .
+For Omnibus package installations, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup .
+For installation from source:
```
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
@@ -113,6 +209,32 @@ sudo -u git crontab -e # Edit the crontab for the git user
Add the following lines at the bottom:
```
-# Create a full backup of the GitLab repositories and SQL database every day at 2am
-0 2 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production
+# Create a full backup of the GitLab repositories and SQL database every day at 4am
+0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1
```
+
+The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
+This is recommended to reduce cron spam.
+
+## Alternative backup strategies
+
+If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
+In this case you can consider using filesystem snapshots as part of your backup strategy.
+
+Example: Amazon EBS
+
+> A GitLab server using omnibus-gitlab hosted on Amazon AWS.
+> An EBS drive containing an ext4 filesystem is mounted at `/var/opt/gitlab`.
+> In this case you could make an application backup by taking an EBS snapshot.
+> The backup includes all repositories, uploads and Postgres data.
+
+Example: LVM snapshots + rsync
+
+> A GitLab server using omnibus-gitlab, with an LVM logical volume mounted at `/var/opt/gitlab`.
+> Replicating the `/var/opt/gitlab` directory using rsync would not be reliable because too many files would change while rsync is running.
+> Instead of rsync-ing `/var/opt/gitlab`, we create a temporary LVM snapshot, which we mount as a read-only filesystem at `/mnt/gitlab_backup`.
+> Now we can have a longer running rsync job which will create a consistent replica on the remote server.
+> The replica includes all repositories, uploads and Postgres data.
+
+If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server.
+It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use.
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index 9e48f56c95..96d67f7b5d 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -8,7 +8,7 @@ Remove namespaces(dirs) from `/home/git/repositories` if they don't exist in Git
# omnibus-gitlab
sudo gitlab-rake gitlab:cleanup:dirs
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:cleanup:dirs RAILS_ENV=production
```
@@ -18,6 +18,6 @@ Remove repositories (global only for now) from `/home/git/repositories` if they
# omnibus-gitlab
sudo gitlab-rake gitlab:cleanup:repos
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:cleanup:repos RAILS_ENV=production
```
diff --git a/doc/raketasks/features.md b/doc/raketasks/features.md
index 99b3d5525b..f9a4619354 100644
--- a/doc/raketasks/features.md
+++ b/doc/raketasks/features.md
@@ -6,7 +6,7 @@ This command will enable the namespaces feature introduced in v4.0. It will move
Note:
-- Because the **repository location will change**, you will need to **update all your git url's** to point to the new location.
+- Because the **repository location will change**, you will need to **update all your git URLs** to point to the new location.
- Username can be changed at [Profile / Account](/profile/account)
**Example:**
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index 39b1a52a44..8a38937062 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -1,28 +1,62 @@
-# Import
+# Import bare repositories into your GitLab instance
-### Import bare repositories into GitLab project instance
+## Notes
-Notes:
+- The owner of the project will be the first admin
+- The groups will be created as needed
+- The owner of the group will be the first admin
+- Existing projects will be skipped
-* project owner will be a first admin
-* groups will be created as needed
-* group owner will be the first admin
-* existing projects will be skipped
+## How to use
-How to use:
+### Create a new folder inside the git repositories path. This will be the name of the new group.
-1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path)
-2. run the command below
+- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed
+it in the `/etc/gitlab/gitlab.rb` file.
+- For installations from source, it is usually located at: `/home/git/repositories` or you can see where
+your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry.
+
+New folder needs to have git user ownership and read/write/execute access for git user and its group:
```
-# omnibus-gitlab
-sudo gitlab-rake gitlab:import:repos
-
-# installation from source or cookbook
-bundle exec rake gitlab:import:repos RAILS_ENV=production
+sudo -u git mkdir /var/opt/gitlab/git-data/repositories/new_group
```
-Example output:
+If you are using an installation from source, replace `/var/opt/gitlab/git-data`
+with `/home/git`.
+
+### Copy your bare repositories inside this newly created folder:
+
+```
+sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repositories/new_group/
+
+# Do this once when you are done copying git repositories
+sudo chown -R git:git /var/opt/gitlab/git-data/repositories/new_group/
+```
+
+`foo.git` needs to be owned by the git user and git users group.
+
+If you are using an installation from source, replace `/var/opt/gitlab/git-data`
+with `/home/git`.
+
+### Run the command below depending on your type of installation:
+
+#### Omnibus Installation
+
+```
+$ sudo gitlab-rake gitlab:import:repos
+```
+
+#### Installation from source
+
+Before running this command you need to change the directory to where your GitLab installation is located:
+
+```
+$ cd /home/git/gitlab
+$ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production
+```
+
+#### Example output
```
Processing abcd.git
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index a0901cc407..41a994f3f6 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -8,7 +8,7 @@ This command gathers information about your GitLab installation and the System i
# omnibus-gitlab
sudo gitlab-rake gitlab:env:info
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:env:info RAILS_ENV=production
```
@@ -16,30 +16,31 @@ Example output:
```
System information
-System: Debian 6.0.7
-Current User: git
-Using RVM: no
-Ruby Version: 2.0.0-p481
-Gem Version: 1.8.23
-Bundler Version:1.3.5
-Rake Version: 10.0.4
+System: Debian 7.8
+Current User: git
+Using RVM: no
+Ruby Version: 2.1.5p273
+Gem Version: 2.4.3
+Bundler Version: 1.7.6
+Rake Version: 10.3.2
+Sidekiq Version: 2.17.8
GitLab information
-Version: 5.1.0.beta2
-Revision: 4da8b37
-Directory: /home/git/gitlab
-DB Adapter: mysql2
-URL: http://example.com
-HTTP Clone URL: http://example.com/some-project.git
-SSH Clone URL: git@example.com:some-project.git
-Using LDAP: no
-Using Omniauth: no
+Version: 7.7.1
+Revision: 41ab9e1
+Directory: /home/git/gitlab
+DB Adapter: postgresql
+URL: https://gitlab.example.com
+HTTP Clone URL: https://gitlab.example.com/some-project.git
+SSH Clone URL: git@gitlab.example.com:some-project.git
+Using LDAP: no
+Using Omniauth: no
GitLab Shell
-Version: 1.2.0
-Repositories: /home/git/repositories/
-Hooks: /home/git/gitlab-shell/hooks/
-Git: /usr/bin/git
+Version: 2.4.1
+Repositories: /home/git/repositories/
+Hooks: /home/git/gitlab-shell/hooks/
+Git: /usr/bin/git
```
## Check GitLab configuration
@@ -59,7 +60,7 @@ You may also have a look at our [Trouble Shooting Guide](https://github.com/gitl
# omnibus-gitlab
sudo gitlab-rake gitlab:check
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:check RAILS_ENV=production
```
@@ -115,8 +116,63 @@ Checking GitLab ... Finished
This will create satellite repositories for all your projects.
-If necessary, remove the `tmp/repo_satellites` directory and rerun the command below.
+If necessary, remove the `repo_satellites` directory and rerun the commands below.
```
-bundle exec rake gitlab:satellites:create RAILS_ENV=production
+sudo -u git -H mkdir -p /home/git/gitlab-satellites
+sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
+sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
+```
+
+## Rebuild authorized_keys file
+
+In some case it is necessary to rebuild the `authorized_keys` file.
+
+For Omnibus-packages:
+```
+sudo gitlab-rake gitlab:shell:setup
+```
+
+For installations from source:
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
+```
+
+```
+This will rebuild an authorized_keys file.
+You will lose any data stored in authorized_keys file.
+Do you want to continue (yes/no)? yes
+```
+
+## Clear redis cache
+
+If for some reason the dashboard shows wrong information you might want to
+clear Redis' cache.
+
+For Omnibus-packages:
+```
+sudo gitlab-rake cache:clear
+```
+
+For installations from source:
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+## Precompile the assets
+
+Sometimes during version upgrades you might end up with some wrong CSS or
+missing some icons. In that case, try to precompile the assets again.
+
+For Omnibus-packages:
+```
+sudo gitlab-rake assets:precompile
+```
+
+For installations from source:
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
```
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index eb9eebb4b4..80b01ca404 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -6,8 +6,8 @@
# omnibus-gitlab
sudo gitlab-rake gitlab:import:user_to_projects[username@domain.tld]
-# installation from source or cookbook
-bundle exec rake gitlab:import:user_to_projects[username@domain.tld]
+# installation from source
+bundle exec rake gitlab:import:user_to_projects[username@domain.tld] RAILS_ENV=production
```
## Add all users to all projects
@@ -20,8 +20,8 @@ Notes:
# omnibus-gitlab
sudo gitlab-rake gitlab:import:all_users_to_all_projects
-# installation from source or cookbook
-bundle exec rake gitlab:import:all_users_to_all_projects
+# installation from source
+bundle exec rake gitlab:import:all_users_to_all_projects RAILS_ENV=production
```
## Add user as a developer to all groups
@@ -30,8 +30,8 @@ bundle exec rake gitlab:import:all_users_to_all_projects
# omnibus-gitlab
sudo gitlab-rake gitlab:import:user_to_groups[username@domain.tld]
-# installation from source or cookbook
-bundle exec rake gitlab:import:user_to_groups[username@domain.tld]
+# installation from source
+bundle exec rake gitlab:import:user_to_groups[username@domain.tld] RAILS_ENV=production
```
## Add all users to all groups
@@ -44,6 +44,6 @@ Notes:
# omnibus-gitlab
sudo gitlab-rake gitlab:import:all_users_to_all_groups
-# installation from source or cookbook
-bundle exec rake gitlab:import:all_users_to_all_groups
+# installation from source
+bundle exec rake gitlab:import:all_users_to_all_groups RAILS_ENV=production
```
diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md
index e1a58835d8..5a8b94af9b 100644
--- a/doc/raketasks/web_hooks.md
+++ b/doc/raketasks/web_hooks.md
@@ -4,42 +4,42 @@
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook"
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" RAILS_ENV=production
## Add a web hook for projects in a given **NAMESPACE**:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
## Remove a web hook from **ALL** projects using:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook"
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" RAILS_ENV=production
## Remove a web hook from projects in a given **NAMESPACE**:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
## List **ALL** web hooks:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:list
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:list RAILS_ENV=production
## List the web hooks from projects in a given **NAMESPACE**:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:list NAMESPACE=/
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:list NAMESPACE=/ RAILS_ENV=production
> Note: `/` is the global namespace.
diff --git a/doc/release/howto_rc1.md b/doc/release/howto_rc1.md
new file mode 100644
index 0000000000..07c703142d
--- /dev/null
+++ b/doc/release/howto_rc1.md
@@ -0,0 +1,55 @@
+# How to create RC1
+
+The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
+
+### 1. Update the installation guide
+
+1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
+1. Check the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782)
+1. Check the [Git version](/lib/tasks/gitlab/check.rake#L794)
+1. There might be other changes. Ask around.
+
+### 2. Create update guides
+
+[Follow this guide](howto_update_guides.md) to create update guides.
+
+### 3. Code quality indicators
+
+Make sure the code quality indicators are green / good.
+
+- [![Build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
+
+- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq) (master branch)
+
+- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+
+- [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available)
+
+- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
+
+### 4. Run release tool
+
+**Make sure EE `master` has latest changes from CE `master`**
+
+Get release tools
+
+```
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
+```
+
+Release candidate creates stable branch from master.
+So we need to sync master branch between all CE, EE and CI remotes.
+
+```
+bundle exec rake sync
+```
+
+Create release candidate and stable branch:
+
+```
+bundle exec rake release["x.x.0.rc1"]
+```
+
+Now developers can use master for merging new features.
+So you should use stable branch for future code changes related to release.
diff --git a/doc/release/howto_update_guides.md b/doc/release/howto_update_guides.md
new file mode 100644
index 0000000000..23d0959c33
--- /dev/null
+++ b/doc/release/howto_update_guides.md
@@ -0,0 +1,55 @@
+# Create update guides
+
+1. Create: CE update guide from previous version. Like `7.3-to-7.4.md`
+1. Create: CE to EE update guide in EE repository for latest version.
+1. Update: `6.x-or-7.x-to-7.x.md` to latest version.
+1. Create: CI update guide from previous version
+
+It's best to copy paste the previous guide and make changes where necessary.
+The typical steps are listed below with any points you should specifically look at.
+
+#### 0. Any major changes?
+
+List any major changes here, so the user is aware of them before starting to upgrade. For instance:
+
+- Database updates
+- Web server changes
+- File structure changes
+
+#### 1. Stop server
+
+#### 2. Make backup
+
+#### 3. Do users need to update dependencies like `git`?
+
+- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release.
+
+- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release.
+
+#### 4. Get latest code
+
+#### 5. Does GitLab shell need to be updated?
+
+#### 6. Install libs, migrations, etc.
+
+#### 7. Any config files updated since last release?
+
+Check if any of these changed since last release:
+
+- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab)
+- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl)
+-
+- [config/gitlab.yml.example](/config/gitlab.yml.example)
+- [config/unicorn.rb.example](/config/unicorn.rb.example)
+- [config/database.yml.mysql](/config/database.yml.mysql)
+- [config/database.yml.postgresql](/config/database.yml.postgresql)
+- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example)
+- [config/resque.yml.example](/config/resque.yml.example)
+
+#### 8. Need to update init script?
+
+Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab)
+
+#### 9. Start application
+
+#### 10. Check application status
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index 09bdde81dc..cfe01896d8 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -1,173 +1,126 @@
# Monthly Release
-NOTE: This is a guide for GitLab developers.
+NOTE: This is a guide used by the GitLab B.V. developers.
-# **15th - Code Freeze & Release Manager**
+It starts 7 working days before the release.
+The release manager doesn't have to perform all the work but must ensure someone is assigned.
+The current release manager must schedule the appointment of the next release manager.
+The new release manager should create overall issue to track the progress.
-### **1. Stop merging in code, except for important bugfixes**
+## Release Manager
-### **2. Release Manager**
+A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases.
+The release manager has to make sure all the steps below are done and delegated where necessary.
+This person should also make sure this document is kept up to date and issues are created and updated.
-A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated.
+## Take vacations into account
-### **3. Update Changelog**
+The time is measured in weekdays to compensate for weekends.
+Do everything on time to prevent problems due to rush jobs or too little testing time.
+Make sure that you take into account any vacations of maintainers.
+If the release is falling behind immediately warn the team.
-Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing.
+## Create an overall issue and follow it
-### **4. Create an overall issue**
+Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching.
+Replace the dates with actual dates based on the number of workdays before the release.
+All steps from issue template are explained below
```
-15th:
+Xth: (7 working days before the 22nd)
-* Update the changelog (#LINK)
-* Triage the omnibus-gitlab milestone
+- [ ] Code freeze
+- [ ] Update the CE changelog (#LINK)
+- [ ] Update the EE changelog (#LINK)
+- [ ] Update the CI changelog (#LINK)
+- [ ] Triage the omnibus-gitlab milestone
-16th:
+Xth: (6 working days before the 22nd)
-* Merge CE in to EE (#LINK)
-* Close the omnibus-gitlab milestone
+- [ ] Merge CE master in to EE master via merge request (#LINK)
+- [ ] Determine QA person and notify this person
+- [ ] Check the tasks in [how to rc1 guide](howto_rc1.md) and delegate tasks if necessary
+- [ ] Create CE, EE, CI RC1 versions (#LINK)
-17th:
+Xth: (5 working days before the 22nd)
-* Create x.x.0.rc1 (#LINK)
+- [ ] Do QA and fix anything coming out of it (#LINK)
+- [ ] Close the omnibus-gitlab milestone
+- [ ] Prepare the blog post (#LINK)
-18th:
+Xth: (4 working days before the 22nd)
-* Update GitLab.com with rc1 (#LINK)
-* Regression issue and tweet about rc1 (#LINK)
-* Start blog post (#LINK)
+- [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
+- [ ] Update ci.gitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
+- [ ] Create regression issues (CE, CI) (#LINK)
+- [ ] Tweet about rc1 (#LINK)
-21th:
+Xth: (3 working days before the 22nd)
-* Do QA and fix anything coming out of it (#LINK)
+- [ ] Merge CE stable branch into EE stable branch
+
+Xth: (2 working days before the 22nd)
+
+- [ ] Check that everyone is mentioned on the blog post (the reviewer should have done this one working day ago)
+- [ ] Check that MVP is added to the mvp page (source/mvp/index.html in www-gitlab-com)
+
+Xth: (1 working day before the 22nd)
+
+- [ ] Create CE, EE, CI stable versions (#LINK)
+- [ ] Create Omnibus tags and build packages
+- [ ] Update GitLab.com with the stable version (#LINK)
+- [ ] Update ci.gitLab.com with the stable version (#LINK)
22nd:
-* Release CE and EE (#LINK)
-
-23rd:
-
-* Prepare package for GitLab.com release (#LINK)
-
-24th:
-
-* Deploy to GitLab.com (#LINK)
-```
-
-# **16th - Merge the CE into EE**
-
-Do this via a merge request.
-
-# **17th - Create RC1**
-
-The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
-
-### **1. Update the installation guide**
-
-1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
-1. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782)
-1. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794)
-1. There might be other changes. Ask around.
-
-### **2. Create an update guides**
-
-1. Create: CE update guide from previous version. Like `from-6-8-to-6.9`
-1. Create: CE to EE update guide in EE repository for latest version.
-1. Update: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/6.0-to-6.x.md to latest version.
-
-It's best to copy paste the previous guide and make changes where necessary.
-The typical steps are listed below with any points you should specifically look at.
-
-#### 0. Any major changes?
-
-List any major changes here, so the user is aware of them before starting to upgrade. For instance:
-
-- Database updates
-- Web server changes
-- File structure changes
-
-#### 1. Make backup
-
-#### 2. Stop server
-
-#### 3. Do users need to update dependencies like `git`?
-
-- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release.
-
-- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release.
-
-#### 4. Get latest code
-
-#### 5. Does GitLab shell need to be updated?
-
-#### 6. Install libs, migrations, etc.
-
-#### 7. Any config files updated since last release?
-
-Check if any of these changed since last release:
-
--
--
--
--
--
--
-
-#### 8. Need to update init script?
-
-Check if the `init.d/gitlab` script changed since last release:
-
-#### 9. Start application
-
-#### 10. Check application status
-
-### **3. Code quality indicators**
-
-Make sure the code quality indicators are green / good.
-
-- [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
-
-- [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch)
-
-- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
-
-- [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available)
-
-- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
-
-### **4. Set VERSION**
-
-Change version in VERSION to `x.x.0.rc1`.
-
-### **5. Tag**
-
-Create an annotated tag that points to the version change commit:
+- [ ] Release CE, EE and CI (#LINK)
-```
-git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1'
```
-# **18th - Release RC1**
+- - -
-### **1. Update GitLab.com**
+## Code Freeze
-Merge the RC1 EE code into GitLab.com.
-Once the build is green, create a package.
+Stop merging code in master, except for important bug fixes
+
+## Update changelog
+
+Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is
+asked if there is anything missing.
+
+There are three changelogs that need to be updated: CE, EE and CI.
+
+## Create RC1 (CE, EE, CI)
+
+[Follow this How-to guide](howto_rc1.md) to create RC1.
+
+## Prepare CHANGELOG for next release
+
+Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version, usually X.X.X.pre.
+
+## QA
+
+Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X QA" in order to keep track of the progress.
+
+Use the omnibus packages created for RC1 of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md).
+
+**NOTE** Upgrader can only be tested when tags are pushed to all repositories. Do not forget to confirm it is working before releasing. Note that in the issue.
+
+#### Fix anything coming out of the QA
+
+Create an issue with description of a problem, if it is quick fix fix it yourself otherwise contact the team for advice.
+
+**NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted,
+create an issue about it in order to discuss the next steps after the release.
+
+## Update GitLab.com with RC1
+
+Use the omnibus EE packages created for RC1.
+If there are big database migrations consider testing them with the production db on a VM.
Try to deploy in the morning.
It is important to do this as soon as possible, so we can catch any errors before we release the full version.
-### **2. Prepare the blog post**
-
-- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
-- Check the changelog of CE and EE for important changes.
-- Create a WIP MR for the blog post
-- Ask Dmitriy to add screenshots to the WIP MR.
-- Decide with team who will be the MVP user.
-- Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
-- Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor)
-- After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments
-
-### **3. Create a regressions issue**
+## Create a regressions issue
On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text:
@@ -176,116 +129,84 @@ Please do not raise issues directly in this issue but link to issues that might
The decision to create a patch release or not is with the release manager who is assigned to this issue.
The release manager will comment here about the plans for patch releases.
-Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately.
+Assign the issue to the release manager and at mention all members of gitlab core team. If there are any known bugs in the release add them immediately.
-### **4. Tweet**
+## Tweet about RC1
Tweet about the RC release:
> GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE
-# **21st - Preparation**
+## Prepare the blog post
-### **1. Pre QA merge**
+1. Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
+1. Make sure the blog post contains information about the GitLab CI release.
+1. Check the changelog of CE and EE for important changes.
+1. Also check the CI changelog
+1. Add a proposed tweet text to the blog post WIP MR description.
+1. Create a WIP MR for the blog post
+1. Ask Dmitriy (or a team member with OS X) to add screenshots to the WIP MR.
+1. Decide with core team who will be the MVP user.
+1. Create WIP MR for adding MVP to MVP page on website
+1. Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
+1. Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master)
+1. Assign to one reviewer who will fix spelling issues by editing the branch (either with a git client or by using the online editor)
+1. Comment to the reviewer: '@person Please mention the whole team as soon as you are done (3 workdays before release at the latest)'
-Merge CE into EE before doing the QA.
+## Create CE, EE, CI stable versions
-### **2. QA**
-
-Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X release" in order to keep track of the progress.
-
-Use the omnibus packages of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md).
-
-**NOTE** Upgrader can only be tested when tags are pushed to all repositories. Do not forget to confirm it is working before releasing. Note that in the issue.
-
-### **3. Fix anything coming out of the QA**
-
-Create an issue with description of a problem, if it is quick fix fix yourself otherwise contact the team for advice.
-
-# **22nd - Release CE and EE**
-
-For GitLab EE, append `-ee` to the branches and tags.
-
-`x-x-stable-ee`
-
-`v.x.x.0-ee`
-
-Merge CE into EE if needed.
-
-### **1. Create x-x-stable branch and push to the repositories**
+Get release tools
```
-git checkout master
-git pull
-git checkout -b x-x-stable
-git push x-x-stable
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
```
-### **2. Build the Omnibus packages**
+Bump version, create release tag and push to remotes:
+
+```
+bundle exec rake release["x.x.0"]
+```
+
+This will create correct version and tag and push to all CE, EE and CI remotes.
+
+Update [installation.md](/doc/install/installation.md) to the newest version in master.
+
+
+## Create Omnibus tags and build packages
Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md).
This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
-### **3. Set VERSION to x.x.x and push**
+## Update GitLab.com with the stable version
-Change the GITLAB_SHELL_VERSION file in `master` of the CE repository if the version changed.
+- Deploy the package (should not need downtime because of the small difference with RC1)
+- Deploy the package for ci.gitlab.com
-Change the GITLAB_SHELL_VERSION file in `master` of the EE repository if the version changed.
+## Release CE, EE and CI
-Change the VERSION file in `master` branch of the CE repository and commit. Cherry-pick into the `x-x-stable` branch of CE.
+__1. Publish packages for new release__
-Change the VERSION file in `master` branch of the EE repository and commit. Cherry-pick into the `x-x-stable-ee` branch of EE.
+Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository.
-### **4. Create annotated tag vx.x.x**
-
-In `x-x-stable` branch check for the SHA-1 of the commit with VERSION file changed. Tag that commit,
-
-```
-git tag -a vx.x.0 -m 'Version x.x.0' xxxxx
-```
-
-where `xxxxx` is SHA-1.
-
-### **5. Push the tag**
-
-```
-git push origin vx.x.0
-```
-
-### **6. Push to remotes**
-
-For GitLab CE, push to dev, GitLab.com and GitHub.
-
-For GitLab EE, push to the subscribers repo.
-
-Make sure the branch is marked 'protected' on each of the remotes you pushed to.
-
-NOTE: You might not have the rights to push to master on dev. Ask Dmitriy.
-
-### **7. Publish blog for new release**
+__2. Publish blog for new release__
+Doublecheck the everyone has been mentioned in the blog post.
Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository.
-### **8. Tweet to blog**
+__3. Tweet to blog__
Send out a tweet to share the good news with the world.
List the most important features and link to the blog post.
-Proposed tweet for CE "GitLab X.X is released! It brings *** "
+Proposed tweet "Release of GitLab X.X & CI Y.Y! FEATURE, FEATURE and FEATURE <link-to-blog-post> #gitlab"
-### **9. Send out the newsletter**
+Consider creating a post on Hacker News.
-Send out an email to the 'GitLab Newsletter' mailing list on MailChimp.
-Replicate the former release newsletter and modify it accordingly.
-Include a link to the blog post and keep it short.
+## Release new AMIs
-Proposed email text:
-"We have released a new version of GitLab. See our blog post() for more information."
+[Follow this guide](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md)
-# **23rd - Optional Patch Release**
+## Create a WIP blogpost for the next release
-# **24th - Update GitLab.com**
-
-Merge the stable release into GitLab.com. Once the build is green deploy the next morning.
-
-# **25th - Release GitLab CI**
+Create a WIP blogpost using [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md).
diff --git a/doc/release/patch.md b/doc/release/patch.md
index bcc14568fc..4c7b471785 100644
--- a/doc/release/patch.md
+++ b/doc/release/patch.md
@@ -10,22 +10,46 @@ Otherwise include it in the monthly release and note there was a regression fix
## Release Procedure
+### Preparation
+
1. Verify that the issue can be reproduced
1. Note in the 'GitLab X.X regressions' that you will create a patch
1. Create an issue on private GitLab development server
1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier
1. Fix the issue on a feature branch, do this on the private GitLab development server
+1. If it is a security issue, then assign it to the release manager and apply a 'security' label
1. Consider creating and testing workarounds
1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
-1. In a separate commit in the stable branch update the CHANGELOG
-1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
-1. In a separate commit in the stable branch update the VERSION
-1. Create an annotated tag vX.X.X for CE and another patch release for EE `git tag -a vx.x.x -m 'Version x.x.x'`
1. Make sure that the build has passed and all tests are passing
-1. Push the code and the tags to all the CE and EE repositories
-1. Apply the patch to GitLab Cloud and the private GitLab development server
+1. In a separate commit in the master branch update the CHANGELOG
+1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
+1. Merge CE stable branch into EE stable branch
+
+
+### Bump version
+
+Get release tools
+
+```
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
+```
+
+Bump all versions in stable branch, even if the changes affect only EE, CE, or CI. Since all the versions are synced now,
+it doesn't make sense to say upgrade CE to 7.2, EE to 7.3 and CI to 7.1.
+
+Create release tag and push to remotes:
+
+```
+bundle exec rake release["x.x.x"]
+```
+
+### Release
+
1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md)
-1. Cherry-pick the changelog update back into master
-1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog
+1. Apply the patch to GitLab.com and the private GitLab development server
+1. Apply the patch to ci.gitLab.com and the private GitLab CI development server
+1. Create and publish a blog post, see [patch release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/patch_release_blog_template.md)
+1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post
1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only)
-1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the patch is EE only)
+1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md)
diff --git a/doc/release/security.md b/doc/release/security.md
index da442de6ee..60bcfbb6da 100644
--- a/doc/release/security.md
+++ b/doc/release/security.md
@@ -8,20 +8,24 @@ Do a security release when there is a critical issue that needs to be addresses
## Security vulnerability disclosure
-Please report suspected security vulnerabilities in private to , also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
+Please report suspected security vulnerabilities in private to , also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Release Procedure
1. Verify that the issue can be reproduced
1. Acknowledge the issue to the researcher that disclosed it
+1. Inform the release manager that there needs to be a security release
1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server"
+1. The MR with the security fix should get a 'security' label and be assigned to the release manager
+1. Build the package for GitLab.com and do a deploy
+1. Build the package for ci.gitLab.com and do a deploy
+1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md)
1. Create feature branches for the blog post on GitLab.com and link them from the code branch
1. Merge and publish the blog posts
1. Send tweets about the release from `@gitlabhq`
-1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only)
1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq)
-1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number
-1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/)
+1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number. CVE is only needed for bugs that allow someone to own the server (Remote Code Execution) or access to code of projects they are not a member of.
+1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/)
1. Thank the security researcher in an email for their cooperation
1. Update the blog post and the CHANGELOG when we receive the CVE number
diff --git a/doc/security/README.md b/doc/security/README.md
index b89e8cbe02..49dfa6eec7 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -2,3 +2,5 @@
- [Password length limits](password_length_limits.md)
- [Rack attack](rack_attack.md)
+- [Web Hooks and insecure internal web services](webhooks.md)
+- [Information exclusivity](information_exclusivity.md)
diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md
new file mode 100644
index 0000000000..f8e7fc3fd0
--- /dev/null
+++ b/doc/security/information_exclusivity.md
@@ -0,0 +1,9 @@
+# Information exclusivity
+
+Git is a distributed version control system (DVCS).
+This means that everyone that works with the source code has a local copy of the complete repository.
+In GitLab every project member that is not a guest (so reporters, developers and masters) can clone the repository to get a local copy.
+After obtaining this local copy the user can upload the full repository anywhere, including another project under their control or another server.
+The consequence is that you can't build access controls that prevent the intentional sharing of source code by users that have access to the source code.
+This is an inherent feature of a DVCS and all git management systems have this limitation.
+Obviously you can take steps to prevent unintentional sharing and information destruction, this is why only some people are allowed to invite others and nobody can force push a protected branch.
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
new file mode 100644
index 0000000000..1e9d33e87c
--- /dev/null
+++ b/doc/security/webhooks.md
@@ -0,0 +1,13 @@
+# Web Hooks and insecure internal web services
+
+If you have non-GitLab web services running on your GitLab server or within its local network, these may be vulnerable to exploitation via Web Hooks.
+
+With [Web Hooks](../web_hooks/web_hooks.md), you and your project masters and owners can set up URLs to be triggered when specific things happen to projects. Normally, these requests are sent to external web services specifically set up for this purpose, that process the request and its attached data in some appropriate way.
+
+Things get hairy, however, when a Web Hook is set up with a URL that doesn't point to an external, but to an internal service, that may do something completely unintended when the web hook is triggered and the POST request is sent.
+
+Because Web Hook requests are made by the GitLab server itself, these have complete access to everything running on the server (http://localhost:123) or within the server's local network (http://192.168.1.12:345), even if these services are otherwise protected and inaccessible from the outside world.
+
+If a web service does not require authentication, Web Hooks can be used to trigger destructive commands by getting the GitLab server to make POST requests to endpoints like "http://localhost:123/some-resource/delete".
+
+To prevent this type of exploitation from happening, make sure that you are aware of every web service GitLab could potentially have access to, and that all of these are set up to require authentication for every potentially destructive command. Enabling authentication but leaving a default password is not enough.
\ No newline at end of file
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index c87fffd7d2..0acb15896d 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -1,4 +1,73 @@
# SSH
-- [Deploy keys](deploy_keys.md)
-- [SSH](ssh.md)
+## SSH keys
+
+An SSH key allows you to establish a secure connection between your
+computer and GitLab.
+
+Before generating an SSH key, check if your system already has one by
+running `cat ~/.ssh/id_rsa.pub`. If you see a long string starting with
+`ssh-rsa` or `ssh-dsa`, you can skip the ssh-keygen step.
+
+To generate a new SSH key, just open your terminal and use code below. The
+ssh-keygen command prompts you for a location and filename to store the key
+pair and for a password. When prompted for the location and filename, you
+can press enter to use the default.
+
+It is a best practice to use a password for an SSH key, but it is not
+required and you can skip creating a password by pressing enter. Note that
+the password you choose here can't be altered or retrieved.
+
+```bash
+ssh-keygen -t rsa -C "$your_email"
+```
+
+Use the code below to show your public key.
+
+```bash
+cat ~/.ssh/id_rsa.pub
+```
+
+Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your
+user profile. Please copy the complete key starting with `ssh-` and ending
+with your username and host.
+
+Use code below to copy your public key to the clipboard. Depending on your
+OS you'll need to use a different command:
+
+**Windows:**
+```bash
+clip < ~/.ssh/id_rsa.pub
+```
+
+**Mac:**
+```bash
+pbcopy < ~/.ssh/id_rsa.pub
+```
+
+**GNU/Linux (requires xclip):**
+```bash
+xclip -sel clip < ~/.ssh/id_rsa.pub
+```
+
+## Deploy keys
+
+Deploy keys allow read-only access to multiple projects with a single SSH
+key.
+
+This is really useful for cloning repositories to your Continuous
+Integration (CI) server. By using deploy keys, you don't have to setup a
+dummy user account.
+
+If you are a project master or owner, you can add a deploy key in the
+project settings under the section 'Deploy Keys'. Press the 'New Deploy
+Key' button and upload a public SSH key. After this, the machine that uses
+the corresponding private key has read-only access to the project.
+
+You can't add the same deploy key twice with the 'New Deploy Key' option.
+If you want to add the same key to another project, please enable it in the
+list that says 'Deploy keys from projects available to you'. All the deploy
+keys of all the projects you have access to are available. This project
+access can happen through being a direct member of the project, or through
+a group. See `def accessible_deploy_keys` in `app/models/user.rb` for more
+information.
diff --git a/doc/ssh/deploy_keys.md b/doc/ssh/deploy_keys.md
deleted file mode 100644
index dcca8bdc61..0000000000
--- a/doc/ssh/deploy_keys.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Deploy keys
-
-Deploy keys allow read-only access one or multiple projects with a single SSH key.
-
-This is really useful for cloning repositories to your Continuous Integration (CI) server. By using a deploy keys you don't have to setup a dummy user account.
-
-If you are a project master or owner you can add a deploy key in the project settings under the section Deploy Keys. Press the 'New Deploy Key' button and upload a public ssh key. After this the machine that uses the corresponding private key has read-only access to the project.
-
-You can't add the same deploy key twice with the 'New Deploy Key' option. If you want to add the same key to another project please enable it in the list that says 'Deploy keys from projects available to you'. All the deploy keys of all the projects you have access to are available. This project access can happen through being a direct member of the project or through a group. See `def accessible_deploy_keys` in `app/models/user.rb` for more information.
diff --git a/doc/ssh/ssh.md b/doc/ssh/ssh.md
deleted file mode 100644
index d466c1bde7..0000000000
--- a/doc/ssh/ssh.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# SSH keys
-
-SSH key allows you to establish a secure connection between your computer and GitLab
-
-Before generating an SSH key, check if your system already has one by running `cat ~/.ssh/id_rsa.pub` If your see a long string starting with `ssh-rsa` or `ssh-dsa`, you can skip the ssh-keygen step.
-
-To generate a new SSH key just open your terminal and use code below. The ssh-keygen command prompts you for a location and filename to store the key pair and for a password. When prompted for the location and filename you can press enter to use the default.
-It is a best practice to use a password for an SSH key but it is not required and you can skip creating a password by pressing enter.
-Note that the password you choose here can't be altered or retrieved.
-
-```bash
-ssh-keygen -t rsa -C "$your_email"
-```
-
-Use the code below to show your public key.
-
-```bash
-cat ~/.ssh/id_rsa.pub
-```
-
-Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile. Please copy the complete key starting with `ssh-` and ending with your username and host.
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 47f17c1a08..f9b6d37d84 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -1,6 +1,6 @@
# System hooks
-Your GitLab instance can perform HTTP POST requests on the following events: `create_project`, `delete_project`, `create_user`, `delete_user` and `change_team_member`.
+Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`.
System hooks can be used, e.g. for logging or changing information in a LDAP server.
@@ -15,8 +15,8 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"name": "StoreCloud",
"owner_email": "johnsmith@gmail.com",
"owner_name": "John Smith",
- "path": "stormcloud",
- "path_with_namespace": "jsmith/stormcloud",
+ "path": "storecloud",
+ "path_with_namespace": "jsmith/storecloud",
"project_id": 74,
"project_visibility": "private",
}
@@ -50,6 +50,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"project_path": "storecloud",
"user_email": "johnsmith@gmail.com",
"user_name": "John Smith",
+ "user_id": 41,
"project_visibility": "private",
}
```
@@ -66,6 +67,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"project_path": "storecloud",
"user_email": "johnsmith@gmail.com",
"user_name": "John Smith",
+ "user_id": 41,
"project_visibility": "private",
}
```
@@ -93,3 +95,86 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"user_id": 41
}
```
+
+**Key added**
+
+```json
+{
+ "event_name": "key_create",
+ "created_at": "2014-08-18 18:45:16 UTC",
+ "username": "root",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost",
+ "id": 4
+}
+```
+
+**Key removed**
+
+```json
+{
+ "event_name": "key_destroy",
+ "created_at": "2014-08-18 18:45:16 UTC",
+ "username": "root",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost",
+ "id": 4
+}
+```
+
+**Group created:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:54Z",
+ "event_name": "group_create",
+ "name": "StoreCloud",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+ "path": "storecloud",
+ "group_id": 78
+}
+```
+
+**Group removed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:54Z",
+ "event_name": "group_destroy",
+ "name": "StoreCloud",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+ "path": "storecloud",
+ "group_id": 78
+}
+```
+
+**New Group Member:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_add_to_group",
+ "group_access": "Master",
+ "group_id": 78,
+ "group_name": "StoreCloud",
+ "group_path": "storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+ "user_id": 41
+}
+```
+**Group Member Removed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_remove_from_group",
+ "group_access": "Master",
+ "group_id": 78,
+ "group_name": "StoreCloud",
+ "group_path": "storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+ "user_id": 41
+}
+```
diff --git a/doc/update/2.6-to-3.0.md b/doc/update/2.6-to-3.0.md
index 6aabbe095d..4827ef9501 100644
--- a/doc/update/2.6-to-3.0.md
+++ b/doc/update/2.6-to-3.0.md
@@ -1,4 +1,5 @@
# From 2.6 to 3.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/2.6-to-3.0.md) for the most up to date instructions.*
## 1. Stop server & resque
@@ -22,29 +23,29 @@ sudo -u gitlab bundle exec rake db:migrate RAILS_ENV=production
# !!! Config should be replaced with a new one. Check it after replace
cp config/gitlab.yml.example config/gitlab.yml
-# update gitolite hooks
+# update Gitolite hooks
-# GITOLITE v2:
+# Gitolite v2:
sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive
sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive
-# GITOLITE v3:
+# Gitolite v3:
sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive
sudo chown git:git /home/git/.gitolite/hooks/common/post-receive
# set valid path to hooks in gitlab.yml in git_host section
# like this
git_host:
- # gitolite 2
+ # Gitolite 2
hooks_path: /home/git/share/gitolite/hooks
- # gitolite 3
+ # Gitolite 3
hooks_path: /home/git/.gitolite/hooks/
-# Make some changes to gitolite config
+# Make some changes to Gitolite config
# For more information visit https://github.com/gitlabhq/gitlabhq/pull/1719
-# gitolite v2
+# Gitolite v2
sudo -u git -H sed -i 's/\(GL_GITCONFIG_KEYS\s*=>*\s*\).\{2\}/\\1"\.\*"/g' /home/git/.gitolite.rc
# gitlite v3
diff --git a/doc/update/2.9-to-3.0.md b/doc/update/2.9-to-3.0.md
index 8af86b0dc9..f4a997a8c5 100644
--- a/doc/update/2.9-to-3.0.md
+++ b/doc/update/2.9-to-3.0.md
@@ -1,4 +1,5 @@
# From 2.9 to 3.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/2.9-to-3.0.md) for the most up to date instructions.*
## 1. Stop server & resque
diff --git a/doc/update/3.0-to-3.1.md b/doc/update/3.0-to-3.1.md
index 3206df3499..a30485c42f 100644
--- a/doc/update/3.0-to-3.1.md
+++ b/doc/update/3.0-to-3.1.md
@@ -1,4 +1,5 @@
# From 3.0 to 3.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/3.0-to-3.1.md) for the most up to date instructions.*
**IMPORTANT!**
diff --git a/doc/update/3.1-to-4.0.md b/doc/update/3.1-to-4.0.md
index 165f4e6a30..f1ef4df474 100644
--- a/doc/update/3.1-to-4.0.md
+++ b/doc/update/3.1-to-4.0.md
@@ -1,4 +1,5 @@
# From 3.1 to 4.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/3.1-to-4.0.md) for the most up to date instructions.*
## Important changes
diff --git a/doc/update/4.0-to-4.1.md b/doc/update/4.0-to-4.1.md
index 4149ed6b08..d89d523591 100644
--- a/doc/update/4.0-to-4.1.md
+++ b/doc/update/4.0-to-4.1.md
@@ -1,4 +1,5 @@
# From 4.0 to 4.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.0-to-4.1.md) for the most up to date instructions.*
## Important changes
diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md
index 5ee8e8781e..6fe4412ff9 100644
--- a/doc/update/4.1-to-4.2.md
+++ b/doc/update/4.1-to-4.2.md
@@ -1,4 +1,5 @@
# From 4.1 to 4.2
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.1-to-4.2.md) for the most up to date instructions.*
## 1. Stop server & Resque
diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md
index 6ec153f624..f9faf65f95 100644
--- a/doc/update/4.2-to-5.0.md
+++ b/doc/update/4.2-to-5.0.md
@@ -1,4 +1,5 @@
# From 4.2 to 5.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.2-to-5.0.md) for the most up to date instructions.*
## Warning
@@ -10,7 +11,7 @@ GitLab 5.0 is affected by critical security vulnerability CVE-2013-4490.
- Self signed SSL certificates are not supported until GitLab 5.1
- **requires ruby1.9.3**
-## 0. Stop gitlab
+## 0. Stop GitLab
sudo service gitlab stop
@@ -41,22 +42,22 @@ git checkout v1.1.0
# copy config
cp config.yml.example config.yml
-# change url to gitlab instance
-# ! make sure url end with '/' like 'https://gitlab.example/'
+# change URL to GitLab instance
+# ! make sure the URL ends with '/' like 'https://gitlab.example/'
vim config.yml
# rewrite hooks
./support/rewrite-hooks.sh
# check ruby version for git user ( 1.9 required!! )
-# gitlab shell requires system ruby 1.9
+# GitLab shell requires system ruby 1.9
ruby -v
# exit from git user
exit
```
-## 4. Copy gitlab instance to git user
+## 4. Copy GitLab instance to git user
```bash
sudo cp -R /home/gitlab/gitlab /home/git/gitlab
@@ -111,7 +112,7 @@ sudo chmod -R u+rwX /home/git/gitlab/tmp/pids
```
-## 6. Update init.d script and nginx config
+## 6. Update init.d script and Nginx config
```bash
# init.d
@@ -123,7 +124,7 @@ sudo chmod +x /etc/init.d/gitlab
sudo -u git -H cp /home/git/gitlab/config/unicorn.rb /home/git/gitlab/config/unicorn.rb.old
sudo -u git -H cp /home/git/gitlab/config/unicorn.rb.example /home/git/gitlab/config/unicorn.rb
-#nginx
+# Nginx
# Replace path from '/home/gitlab/' to '/home/git/'
sudo vim /etc/nginx/sites-enabled/gitlab
sudo service nginx restart
@@ -137,7 +138,7 @@ sudo service gitlab start
# check if unicorn and sidekiq started
# If not try to logout, also check replaced path from '/home/gitlab/' to '/home/git/'
-# in nginx, unicorn, init.d etc
+# in Nginx, unicorn, init.d etc
ps aux | grep unicorn
ps aux | grep sidekiq
@@ -162,8 +163,49 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
-**P.S. If everything works as expected you can remove gitlab user from system**
+## 9. Cleanup
+
+**If everything works as expected you can cleanup some old things**
+Recommend you wait a bit and do a backup before completing the following.
```bash
+# remove GitLab user from system
sudo userdel -r gitlab
+
+cd /home/git
+
+# cleanup .profile
+## remove text from .profile added during gitolite installation:
+## PATH=\$PATH:/home/git/bin
+## export PATH
+## to see what a clean .profile for new users on your system would look like see /etc/skel/.profile
+sudo -u git -H vim .profile
+
+# remove gitolite
+sudo rm -R bin
+sudo rm -Rf gitolite
+sudo rm -R .gitolite
+sudo rm .gitolite.rc
+sudo rm -f gitlab.pub
+sudo rm projects.list
+
+# reset tmp folders
+sudo service gitlab stop
+cd /home/git/gitlab
+sudo rm -R tmp
+sudo -u git -H mkdir tmp
+sudo chmod -R u+rwX tmp/
+
+# create directory for pids, make sure GitLab can write to it
+sudo -u git -H mkdir tmp/pids/
+sudo chmod -R u+rwX tmp/pids/
+
+# if you are already running a newer version of GitLab check that installation guide for other tmp folders you need to create
+
+# reboot system
+sudo reboot
+
+# login, check that GitLab is running fine
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md
index 0e597abb1a..9fbd1f8851 100644
--- a/doc/update/5.0-to-5.1.md
+++ b/doc/update/5.0-to-5.1.md
@@ -1,4 +1,5 @@
# From 5.0 to 5.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.0-to-5.1.md) for the most up to date instructions.*
## Warning
diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md
index 6ef559ac9f..cf9c4e4f77 100644
--- a/doc/update/5.1-to-5.2.md
+++ b/doc/update/5.1-to-5.2.md
@@ -1,4 +1,5 @@
# From 5.1 to 5.2
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-5.2.md) for the most up to date instructions.*
## Warning
diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md
index 8ec56b266c..97a98ede07 100644
--- a/doc/update/5.1-to-5.4.md
+++ b/doc/update/5.1-to-5.4.md
@@ -1,4 +1,5 @@
# From 5.1 to 5.4
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-5.4.md) for the most up to date instructions.*
Also works starting from 5.2.
diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md
index 8870f5bc85..a3fdd92bd2 100644
--- a/doc/update/5.1-to-6.0.md
+++ b/doc/update/5.1-to-6.0.md
@@ -1,4 +1,5 @@
# From 5.1 to 6.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-6.0.md) for the most up to date instructions.*
## Warning
@@ -28,7 +29,7 @@ Any changes to group members will immediately be reflected in the project permis
You can even have multiple owners for a group, greatly simplifying administration.
-## 0. Backup
+## 0. Backup & prepare for update
It's useful to make a backup just in case things go south:
(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
@@ -38,6 +39,72 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
+The migrations in this update are very sensitive to incomplete or inconsistent data. If you have a long-running GitLab installation and some of the previous upgrades did not work out 100% correct this may bite you now. The following can help you have a more smooth upgrade.
+
+### Find projects with invalid project names
+
+#### MySQL
+Login to MySQL:
+
+ mysql -u root -p
+
+Find projects with invalid names:
+
+```bash
+mysql> use gitlabhq_production;
+
+# find projects with invalid first char, projects must start with letter
+mysql> select name from projects where name REGEXP '^[^A-Za-z]';
+
+# find projects with other invalid chars
+## names must only contain alphanumeric chars, underscores, spaces, periods, and dashes
+mysql> select name from projects where name REGEXP '[^a-zA-Z0-9_ .-]+';
+```
+
+If any projects have invalid names try correcting them from the web interface before starting the upgrade.
+If correcting them from the web interface fails you can correct them using MySQL:
+
+```bash
+# e.g. replace invalid / with allowed _
+mysql> update projects set name = REPLACE(name,'/','_');
+# repeat for all invalid chars found in project names
+```
+
+#### PostgreSQL
+Make sure all project names start with a letter and only contain alphanumeric chars, underscores, spaces, periods, and dashes (a-zA-Z0-9_ .-).
+
+### Find other common errors
+
+```
+cd /home/git/gitlab
+# Start rails console
+sudo -u git -H bin/rails console production
+
+# Make sure none of the following rails commands return results
+
+# All project owners should have an owner:
+Project.all.select { |project| project.owner.blank? }
+
+# Every user should have a namespace:
+User.all.select { |u| u.namespace.blank? }
+
+# Projects in the global namespace should not conflict with projects in the owner namespace:
+Project.where(namespace_id: nil).select { |p| Project.where(path: p.path, namespace_id: p.owner.try(:namespace).try(:id)).present? }
+```
+
+If any of the above rails commands returned results other than `=> []` try correcting the issue from the web interface.
+
+If you find projects without an owner (first rails command above), correct it. For MySQL setups:
+
+```bash
+# get your user id
+mysql> select id, name from users order by name;
+
+# set yourself as owner of project
+# replace your_user_id with your user id and bad_project_id with the project id from the rails command
+mysql> update projects set creator_id=your_user_id where id=bad_project_id;
+```
+
## 1. Stop server
sudo service gitlab stop
@@ -147,31 +214,3 @@ Follow the [upgrade guide from 5.0 to 5.1](5.0-to-5.1.md), except for the databa
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
-
-## Troubleshooting
-
-The migrations in this update are very sensitive to incomplete or inconsistent data. If you have a long-running GitLab installation and some of the previous upgrades did not work out 100% correct this may bite you now. The following commands can be run in the rails console to look for 'bad' data.
-
-Start rails console:
-
-```
-sudo -u git -H rails console production
-```
-
-All project owners should have an owner:
-
-```
-Project.all.select { |project| project.owner.blank? }
-```
-
-Every user should have a namespace:
-
-```
-User.all.select { |u| u.namespace.blank? }
-```
-
-Projects in the global namespace should not conflict with projects in the owner namespace:
-
-```
-Project.where(namespace_id: nil).select { |p| Project.where(path: p.path, namespace_id: p.owner.try(:namespace).try(:id)).present? }
-```
diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md
index 61ddf13564..27613aeda0 100644
--- a/doc/update/5.2-to-5.3.md
+++ b/doc/update/5.2-to-5.3.md
@@ -1,4 +1,5 @@
# From 5.2 to 5.3
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.2-to-5.3.md) for the most up to date instructions.*
## Warning
diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md
index 8a0d43e3e6..577b9a585f 100644
--- a/doc/update/5.3-to-5.4.md
+++ b/doc/update/5.3-to-5.4.md
@@ -1,4 +1,5 @@
# From 5.3 to 5.4
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.3-to-5.4.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md
index 7bf7bce6aa..d9c6d9bfb9 100644
--- a/doc/update/5.4-to-6.0.md
+++ b/doc/update/5.4-to-6.0.md
@@ -1,16 +1,20 @@
# From 5.4 to 6.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.4-to-6.0.md) for the most up to date instructions.*
## Warning
GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
+**You need to follow this guide first, before updating past 6.0, as it contains critical migration steps that are only present
+in the `6-0-stable` branch**
+
## Deprecations
### Global projects
The root (global) namespace for projects is deprecated.
-So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote url. Please make sure you disable sending email when you do a test of the upgrade.
+So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote URL. Please make sure you disable sending email when you do a test of the upgrade.
### Teams
diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md
index b8df16bfd9..c5eba1c01c 100644
--- a/doc/update/6.0-to-6.1.md
+++ b/doc/update/6.0-to-6.1.md
@@ -1,4 +1,5 @@
# From 6.0 to 6.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.0-to-6.1.md) for the most up to date instructions.*
## Warning
@@ -73,7 +74,6 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
```bash
sudo rm /etc/init.d/gitlab
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-sudo chmod +x /etc/init.d/gitlab
```
## 7. Start application
diff --git a/doc/update/6.0-to-7.1.md b/doc/update/6.0-to-7.1.md
deleted file mode 100644
index 84deaf3376..0000000000
--- a/doc/update/6.0-to-7.1.md
+++ /dev/null
@@ -1,182 +0,0 @@
-# From 6.0 to 7.1
-
-## Deprecations
-
-The 'Wall' feature has been removed in GitLab 7.1. Existing wall comments will remain stored in the database after the upgrade.
-
-## Global issue numbers
-
-As of 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their URL. If you use an old issue number URL and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects.
-
-## 0. Backup
-
-It's useful to make a backup just in case things go south:
-(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
-
-```bash
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
-```
-
-## 1. Stop server
-
- sudo service gitlab stop
-
-## 2. Update Ruby
-
-If you are still using Ruby 1.9.3 or below, you will need to update Ruby.
-You can check which version you are running with `ruby -v`.
-
-If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons.
-
-If you are running Ruby 2.1.1 consider upgrading to 2.1.2, because of the high memory usage of Ruby 2.1.1.
-
-Install, update dependencies:
-
-```bash
-sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl
-```
-
-Download and compile Ruby:
-
-```bash
-mkdir /tmp/ruby && cd /tmp/ruby
-curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
-cd ruby-2.1.2
-./configure --disable-install-rdoc
-make
-sudo make install
-```
-
-Install Bundler:
-
-```bash
-sudo gem install bundler --no-ri --no-rdoc
-```
-
-## 3. Get latest code
-
-```bash
-cd /home/git/gitlab
-sudo -u git -H git fetch --all
-```
-
-For GitLab Community Edition:
-
-```bash
-sudo -u git -H git checkout 7-1-stable
-```
-
-OR
-
-For GitLab Enterprise Edition:
-
-```bash
-sudo -u git -H git checkout 7-1-stable-ee
-```
-
-
-## 4. Install additional packages
-
-```bash
-# Add support for lograte for better log file handling
-sudo apt-get install logrotate
-```
-
-## 5. Update gitlab-shell
-
-```bash
-cd /home/git/gitlab-shell
-sudo -u git -H git fetch
-sudo -u git -H git checkout v1.9.6 # Addresses multiple critical security vulnerabilities
-```
-
-## 6. Install libs, migrations, etc.
-
-```bash
-cd /home/git/gitlab
-
-# MySQL installations (note: the line below states '--without ... postgres')
-sudo -u git -H bundle install --without development test postgres --deployment
-
-# PostgreSQL installations (note: the line below states '--without ... mysql')
-sudo -u git -H bundle install --without development test mysql --deployment
-
-
-# Run database migrations
-sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
-
-# Enable internal issue IDs (introduced in GitLab 6.1)
-sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
-
-# Clean up assets and cache
-sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
-
-# Close access to gitlab-satellites for others
-sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
-```
-
-## 7. Update config files
-
-TIP: to see what changed in gitlab.yml.example in this release use next command:
-
-```
-git diff 6-0-stable:config/gitlab.yml.example 7-1-stable:config/gitlab.yml.example
-```
-
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-1-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-1-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v1.9.6/config.yml.example but with your settings.
-* Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-1-stable/lib/support/nginx/gitlab but with your settings.
-* Copy rack attack middleware config
-
-```bash
-sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
-```
-
-* Set up logrotate
-
-```bash
-sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
-```
-
-## 8. Update Init script
-
-```bash
-sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-```
-
-## 9. Start application
-
- sudo service gitlab start
- sudo service nginx restart
-
-## 10. Check application status
-
-Check if GitLab and its environment are configured correctly:
-
- cd /home/git/gitlab
- sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
-
-To make sure you didn't miss anything run a more thorough check with:
-
- sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
-
-If all items are green, then congratulations upgrade complete!
-
-## Things went south? Revert to previous version (6.0)
-
-### 1. Revert the code to the previous version
-
-Follow the [upgrade guide from 5.4 to 6.0](5.4-to-6.0.md), except for the database migration (the backup is already migrated to the previous version).
-
-### 2. Restore from the backup:
-
-```bash
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
-```
-
-## Login issues after upgrade?
-
-If running in HTTPS mode, be sure to read [Can't Verify CSRF token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page)
diff --git a/doc/update/6.0-to-7.2.md b/doc/update/6.0-to-7.2.md
deleted file mode 100644
index 51e260b9e6..0000000000
--- a/doc/update/6.0-to-7.2.md
+++ /dev/null
@@ -1,194 +0,0 @@
-# From 6.0 to 7.2
-
-## Global issue numbers
-
-As of 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their URL. If you use an old issue number URL and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects.
-
-## Editable labels
-
-In GitLab 7.2 we replace Issue and Merge Request tags with labels, making it
-possible to edit the label text and color. The characters '?', '&' and ',' are
-no longer allowed however so those will be removed from your tags during the
-database migrations for GitLab 7.2.
-
-## 0. Backup
-
-It's useful to make a backup just in case things go south:
-(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
-
-```bash
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
-```
-
-## 1. Stop server
-
- sudo service gitlab stop
-
-## 2. Update Ruby
-
-If you are still using Ruby 1.9.3 or below, you will need to update Ruby.
-You can check which version you are running with `ruby -v`.
-
-If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons.
-
-If you are running Ruby 2.1.1 consider upgrading to 2.1.2, because of the high memory usage of Ruby 2.1.1.
-
-Install, update dependencies:
-
-```bash
-sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl
-```
-
-Download and compile Ruby:
-
-```bash
-mkdir /tmp/ruby && cd /tmp/ruby
-curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
-cd ruby-2.1.2
-./configure --disable-install-rdoc
-make
-sudo make install
-```
-
-Install Bundler:
-
-```bash
-sudo gem install bundler --no-ri --no-rdoc
-```
-
-## 3. Get latest code
-
-```bash
-cd /home/git/gitlab
-sudo -u git -H git fetch --all
-```
-
-For GitLab Community Edition:
-
-```bash
-sudo -u git -H git checkout 7-2-stable
-```
-
-OR
-
-For GitLab Enterprise Edition:
-
-```bash
-sudo -u git -H git checkout 7-2-stable-ee
-```
-
-
-## 4. Install additional packages
-
-```bash
-# Add support for lograte for better log file handling
-sudo apt-get install logrotate
-
-# Install pkg-config and cmake, which is needed for the latest versions of rugged
-sudo apt-get install pkg-config cmake
-```
-
-## 5. Update gitlab-shell
-
-```bash
-cd /home/git/gitlab-shell
-sudo -u git -H git fetch
-sudo -u git -H git checkout v1.9.7
-```
-
-## 6. Install libs, migrations, etc.
-
-```bash
-cd /home/git/gitlab
-
-# MySQL installations (note: the line below states '--without ... postgres')
-sudo -u git -H bundle install --without development test postgres --deployment
-
-# PostgreSQL installations (note: the line below states '--without ... mysql')
-sudo -u git -H bundle install --without development test mysql --deployment
-
-
-# Run database migrations
-sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
-
-# Enable internal issue IDs (introduced in GitLab 6.1)
-sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
-
-# Clean up assets and cache
-sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
-
-# Close access to gitlab-satellites for others
-sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
-```
-
-## 7. Update config files
-
-TIP: to see what changed in gitlab.yml.example in this release use next command:
-
-```
-git diff 6-0-stable:config/gitlab.yml.example 7-2-stable:config/gitlab.yml.example
-```
-
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-2-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-2-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v1.9.7/config.yml.example but with your settings.
-* Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-2-stable/lib/support/nginx/gitlab but with your settings.
-* Copy rack attack middleware config
-
-```bash
-sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
-```
-
-* Set up logrotate
-
-```bash
-sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
-```
-
-## 8. Update Init script
-
-```bash
-sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-```
-
-## 9. Start application
-
- sudo service gitlab start
- sudo service nginx restart
-
-## 10. Check application status
-
-Check if GitLab and its environment are configured correctly:
-
- cd /home/git/gitlab
- sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
-
-To make sure you didn't miss anything run a more thorough check with:
-
- sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
-
-If all items are green, then congratulations upgrade complete!
-
-## 11. Update OmniAuth configuration
-
-When using Google omniauth login, changes of the Google account required.
-Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/).
-More details can be found at the [integration documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/google.md).
-
-## Things went south? Revert to previous version (6.0)
-
-### 1. Revert the code to the previous version
-
-Follow the [upgrade guide from 5.4 to 6.0](5.4-to-6.0.md), except for the database migration (the backup is already migrated to the previous version).
-
-### 2. Restore from the backup:
-
-```bash
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
-```
-
-## Login issues after upgrade?
-
-If running in HTTPS mode, be sure to read [Can't Verify CSRF token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page)
diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md
index f189899f57..a534528108 100644
--- a/doc/update/6.1-to-6.2.md
+++ b/doc/update/6.1-to-6.2.md
@@ -1,4 +1,5 @@
# From 6.1 to 6.2
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.1-to-6.2.md) for the most up to date instructions.*
**You should update to 6.1 before installing 6.2 so all the necessary conversions are run.**
@@ -35,7 +36,7 @@ sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulner
## 4. Install additional packages
```bash
-# Add support for lograte for better log file handling
+# Add support for logrotate for better log file handling
sudo apt-get install logrotate
```
@@ -88,7 +89,6 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
```bash
sudo rm /etc/init.d/gitlab
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-sudo chmod +x /etc/init.d/gitlab
```
## 8. Start application
diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md
index aa6ef56990..b08ebde080 100644
--- a/doc/update/6.2-to-6.3.md
+++ b/doc/update/6.2-to-6.3.md
@@ -1,4 +1,5 @@
# From 6.2 to 6.3
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.2-to-6.3.md) for the most up to date instructions.*
**Requires version: 6.1 or 6.2.**
@@ -74,7 +75,6 @@ sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers
```bash
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-sudo chmod +x /etc/init.d/gitlab
```
## 7. Start application
diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md
index 96c2895981..951d92dfeb 100644
--- a/doc/update/6.3-to-6.4.md
+++ b/doc/update/6.3-to-6.4.md
@@ -1,4 +1,5 @@
# From 6.3 to 6.4
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.3-to-6.4.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/6.4-to-6.5.md b/doc/update/6.4-to-6.5.md
index 1624296fc3..0dae9a9fe5 100644
--- a/doc/update/6.4-to-6.5.md
+++ b/doc/update/6.4-to-6.5.md
@@ -1,4 +1,5 @@
# From 6.4 to 6.5
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.4-to-6.5.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/6.5-to-6.6.md b/doc/update/6.5-to-6.6.md
index 544eee17fe..c24e83eb00 100644
--- a/doc/update/6.5-to-6.6.md
+++ b/doc/update/6.5-to-6.6.md
@@ -1,4 +1,5 @@
# From 6.5 to 6.6
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.5-to-6.6.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/6.6-to-6.7.md b/doc/update/6.6-to-6.7.md
index 77ac4d0bfa..b4298c9342 100644
--- a/doc/update/6.6-to-6.7.md
+++ b/doc/update/6.6-to-6.7.md
@@ -1,4 +1,5 @@
# From 6.6 to 6.7
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.6-to-6.7.md) for the most up to date instructions.*
## 0. Backup
@@ -70,6 +71,9 @@ sudo -u git -H gzip /home/git/gitlab-shell/gitlab-shell.log.1
# Close access to gitlab-satellites for others
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
+
+# Add directory for uploads
+sudo -u git -H mkdir -p /home/git/gitlab/public/uploads
```
## 5. Start application
diff --git a/doc/update/6.7-to-6.8.md b/doc/update/6.7-to-6.8.md
index b5b47f8930..4fb90639f1 100644
--- a/doc/update/6.7-to-6.8.md
+++ b/doc/update/6.7-to-6.8.md
@@ -1,4 +1,5 @@
# From 6.7 to 6.8
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.7-to-6.8.md) for the most up to date instructions.*
## 0. Backup
@@ -62,7 +63,6 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-sudo chmod +x /etc/init.d/gitlab
# Close access to gitlab-satellites for others
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
diff --git a/doc/update/6.8-to-6.9.md b/doc/update/6.8-to-6.9.md
index 9efb384ff5..b9b8b63f65 100644
--- a/doc/update/6.8-to-6.9.md
+++ b/doc/update/6.8-to-6.9.md
@@ -1,4 +1,5 @@
# From 6.8 to 6.9
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.8-to-6.9.md) for the most up to date instructions.*
### 0. Backup
diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md
index f1d3d9c7b2..236430b595 100644
--- a/doc/update/6.9-to-7.0.md
+++ b/doc/update/6.9-to-7.0.md
@@ -1,4 +1,5 @@
# From 6.9 to 7.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.9-to-7.0.md) for the most up to date instructions.*
### 0. Backup
@@ -93,7 +94,6 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-sudo chmod +x /etc/init.d/gitlab
```
### 6. Update config files
@@ -106,6 +106,9 @@ There are new configuration options available for gitlab.yml. View them with the
git diff origin/6-9-stable:config/gitlab.yml.example origin/7-0-stable:config/gitlab.yml.example
```
+* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting
+
### 7. Start application
sudo service gitlab start
diff --git a/doc/update/6.x-or-7.x-to-7.10.md b/doc/update/6.x-or-7.x-to-7.10.md
new file mode 100644
index 0000000000..39e12f32d0
--- /dev/null
+++ b/doc/update/6.x-or-7.x-to-7.10.md
@@ -0,0 +1,298 @@
+# From 6.x or 7.x to 7.10
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.10.md) for the most up to date instructions.*
+
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.10.
+
+## Global issue numbers
+
+As of 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their URL. If you use an old issue number URL and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects.
+
+## Editable labels
+
+In GitLab 7.2 we replace Issue and Merge Request tags with labels, making it
+possible to edit the label text and color. The characters `?`, `&` and `,` are
+no longer allowed however so those will be removed from your tags during the
+database migrations for GitLab 7.2.
+
+## 0. Stop server
+
+ sudo service gitlab stop
+
+## 1. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+## 2. Update Ruby
+
+If you are still using Ruby 1.9.3 or below, you will need to update Ruby.
+You can check which version you are running with `ruby -v`.
+
+If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons.
+
+If you are running Ruby 2.1.1 consider upgrading to 2.1.6, because of the high memory usage of Ruby 2.1.1.
+
+Install, update dependencies:
+
+```bash
+sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl
+```
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.6.tar.gz | tar xz
+cd ruby-2.1.6
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+## 3. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-10-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-10-stable-ee
+```
+
+## 4. Install additional packages
+
+```bash
+# Add support for logrotate for better log file handling
+sudo apt-get install logrotate
+
+# Install pkg-config and cmake, which is needed for the latest versions of rugged
+sudo apt-get install pkg-config cmake
+
+# Install Kerberos header files, which are needed for GitLab EE Kerberos support
+sudo apt-get install libkrb5-dev
+
+# Install nodejs, javascript runtime required for assets
+sudo apt-get install nodejs
+```
+
+## 5. Configure Redis to use sockets
+
+ # Configure redis to use sockets
+ sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
+ # Disable Redis listening on TCP by setting 'port' to 0
+ sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
+ # Enable Redis socket for default Debian / Ubuntu path
+ echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
+ sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
+ # Activate the changes to redis.conf
+ sudo service redis-server restart
+ # Add git to the redis group
+ sudo usermod -aG redis git
+
+ # Configure Redis connection settings
+ sudo -u git -H cp config/resque.yml.example config/resque.yml
+ # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration
+ sudo -u git -H editor config/resque.yml
+
+ # Configure gitlab-shell to use Redis sockets
+ sudo -u git -H sed -i 's|^ # socket.*| socket: /var/run/redis/redis.sock|' /home/git/gitlab-shell/config.yml
+
+## 6. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.2
+```
+
+## 7. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Enable internal issue IDs (introduced in GitLab 6.1)
+sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Close access to gitlab-satellites for others
+sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+## 8. Update config files
+
+TIP: to see what changed in `gitlab.yml.example` in this release use next command:
+
+```
+git diff 6-0-stable:config/gitlab.yml.example 7-10-stable:config/gitlab.yml.example
+```
+
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.0/config.yml.example but with your settings.
+* Copy rack attack middleware config
+
+```bash
+sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
+```
+
+* Set up logrotate
+
+```bash
+sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
+```
+
+### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+
+## 9. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+## 10. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ cd /home/git/gitlab
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade complete!
+
+## 11. Update OmniAuth configuration
+
+When using Google omniauth login, changes of the Google account required.
+Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/).
+More details can be found at the [integration documentation](../../../master/doc/integration/google.md).
+
+## 12. Optional optimizations for GitLab setups with MySQL databases
+
+Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Secure your MySQL installation (added in GitLab 6.2)
+sudo mysql_secure_installation
+
+# Login to MySQL
+mysql -u root -p
+
+# do not type the 'mysql>', this is part of the prompt
+
+# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all shown SQL statements
+
+# Convert all tables to correct character set
+SET foreign_key_checks = 0;
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all shown SQL statements
+
+# turn foreign key checks back on
+SET foreign_key_checks = 1;
+
+# Find MySQL users
+mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
+
+# If git user exists and gitlab user does not exist
+# you are done with the database cleanup tasks
+mysql> \q
+
+# If both users exist skip to Delete gitlab user
+
+# Create new user for GitLab (changed in GitLab 6.4)
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+
+# Grant the git user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Delete the old gitlab user
+mysql> DELETE FROM mysql.user WHERE user='gitlab';
+
+# Quit the database session
+mysql> \q
+
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
+
+# Type the password you replaced $password with earlier
+
+# You should now see a 'mysql>' prompt
+
+# Quit the database session
+mysql> \q
+
+# Update database configuration details
+# See config/database.yml.mysql for latest recommended configuration details
+# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
+# Set production -> pool: 10 (updated in GitLab 5.3)
+# Set production -> username: git
+# Set production -> password: the password your replaced $password with earlier
+sudo -u git -H editor /home/git/gitlab/config/database.yml
+```
+
+## Things went south? Revert to previous version (7.0)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 6.9 to 7.0](6.9-to-7.0.md), except for the database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+## Login issues after upgrade?
+
+If running in HTTPS mode, be sure to read [Can't Verify CSRF token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page)
diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md
index 166ff0ea13..a4e9be9946 100644
--- a/doc/update/7.0-to-7.1.md
+++ b/doc/update/7.0-to-7.1.md
@@ -1,4 +1,5 @@
# From 7.0 to 7.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.0-to-7.1.md) for the most up to date instructions.*
### 0. Backup
@@ -93,7 +94,6 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-sudo chmod +x /etc/init.d/gitlab
```
### 6. Update config files
diff --git a/doc/update/7.1-to-7.2.md b/doc/update/7.1-to-7.2.md
index 04b9ce76a1..88cb63d7d4 100644
--- a/doc/update/7.1-to-7.2.md
+++ b/doc/update/7.1-to-7.2.md
@@ -1,9 +1,10 @@
# From 7.1 to 7.2
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.1-to-7.2.md) for the most up to date instructions.*
## Editable labels
In GitLab 7.2 we replace Issue and Merge Request tags with labels, making it
-possible to edit the label text and color. The characters '?', '&' and ',' are
+possible to edit the label text and color. The characters `?`, `&` and `,` are
no longer allowed however so those will be removed from your tags during the
database migrations for GitLab 7.2.
@@ -46,7 +47,7 @@ sudo -u git -H git checkout 7-2-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v1.9.7
+sudo -u git -H git checkout v1.9.8
```
### 4. Install new system dependencies
@@ -77,19 +78,21 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-sudo chmod +x /etc/init.d/gitlab
```
### 6. Update config files
-#### New configuration options for gitlab.yml
+#### New configuration options for `gitlab.yml`
-There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+There are new configuration options available for `gitlab.yml`. View them with the command below and apply them to your current `gitlab.yml`.
```
git diff 7-1-stable:config/gitlab.yml.example 7-2-stable:config/gitlab.yml.example
```
+* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting
+
Update rack attack middleware config
```
diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md
new file mode 100644
index 0000000000..18f77d6396
--- /dev/null
+++ b/doc/update/7.2-to-7.3.md
@@ -0,0 +1,145 @@
+# From 7.2 to 7.3
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.2-to-7.3.md) for the most up to date instructions.*
+
+### 0. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-3-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-3-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.0.1
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+
+### 5. Configure Redis to use sockets
+
+ # Configure redis to use sockets
+ sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
+ # Disable Redis listening on TCP by setting 'port' to 0
+ sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
+ # Enable Redis socket for default Debian / Ubuntu path
+ echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
+ sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
+ # Activate the changes to redis.conf
+ sudo service redis-server restart
+ # Add git to the redis group
+ sudo usermod -aG redis git
+
+ # Configure Redis connection settings
+ sudo -u git -H cp config/resque.yml.example config/resque.yml
+ # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration
+ sudo -u git -H editor config/resque.yml
+
+ # Configure gitlab-shell to use Redis sockets
+ sudo -u git -H sed -i 's|^ # socket.*| socket: /var/run/redis/redis.sock|' /home/git/gitlab-shell/config.yml
+
+### 6. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+
+```
+git diff origin/7-2-stable:config/gitlab.yml.example origin/7-3-stable:config/gitlab.yml.example
+```
+
+```
+# Use the default Unicorn socket backlog value of 1024
+sudo -u git -H sed -i 's/:backlog => 64/:backlog => 1024/' config/unicorn.rb
+```
+
+* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your setting
+
+### 7. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 8. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+### 9. Update OmniAuth configuration
+
+When using Google omniauth login, changes of the Google account required.
+Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/).
+More details can be found at the [integration documentation](../integration/google.md).
+
+## Things went south? Revert to previous version (7.2)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.1 to 7.2](7.1-to-7.2.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md
new file mode 100644
index 0000000000..53e739c06f
--- /dev/null
+++ b/doc/update/7.3-to-7.4.md
@@ -0,0 +1,197 @@
+# From 7.3 to 7.4
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.3-to-7.4.md) for the most up to date instructions.*
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-4-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-4-stable-ee
+```
+
+### 3. Install libs, migrations, etc.
+
+```bash
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 4. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+
+```
+git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gitlab.yml.example
+```
+
+#### Change timeout for unicorn
+
+```
+# set timeout to 60
+sudo -u git -H editor config/unicorn.rb
+```
+
+#### Change Nginx HTTPS settings
+
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting
+
+#### MySQL Databases: Update database.yml config file
+
+* Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql](/config/database.yml.mysql)
+
+```
+sudo -u git -H editor config/database.yml
+```
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+
+### 7. Optional optimizations for GitLab setups with MySQL databases
+
+Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Secure your MySQL installation (added in GitLab 6.2)
+sudo mysql_secure_installation
+
+# Login to MySQL
+mysql -u root -p
+
+# do not type the 'mysql>', this is part of the prompt
+
+# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all shown SQL statements
+
+# Convert all tables to correct character set
+SET foreign_key_checks = 0;
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all shown SQL statements
+
+# turn foreign key checks back on
+SET foreign_key_checks = 1;
+
+# Find MySQL users
+mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
+
+# If git user exists and gitlab user does not exist
+# you are done with the database cleanup tasks
+mysql> \q
+
+# If both users exist skip to Delete gitlab user
+
+# Create new user for GitLab (changed in GitLab 6.4)
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+
+# Grant the git user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Delete the old gitlab user
+mysql> DELETE FROM mysql.user WHERE user='gitlab';
+
+# Quit the database session
+mysql> \q
+
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
+
+# Type the password you replaced $password with earlier
+
+# You should now see a 'mysql>' prompt
+
+# Quit the database session
+mysql> \q
+
+# Update database configuration details
+# See config/database.yml.mysql for latest recommended configuration details
+# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
+# Set production -> pool: 10 (updated in GitLab 5.3)
+# Set production -> username: git
+# Set production -> password: the password your replaced $password with earlier
+sudo -u git -H editor /home/git/gitlab/config/database.yml
+
+# Start GitLab
+sudo service gitlab start
+sudo service nginx restart
+
+# Run thorough check
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+
+## Things went south? Revert to previous version (7.3)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.2 to 7.3](7.2-to-7.3.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+
+
+
diff --git a/doc/update/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md
new file mode 100644
index 0000000000..673eab3c56
--- /dev/null
+++ b/doc/update/7.4-to-7.5.md
@@ -0,0 +1,108 @@
+# From 7.4 to 7.5
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-5-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-5-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.2.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+
+```
+git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (7.4)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.3 to 7.4](7.3-to-7.4.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md
new file mode 100644
index 0000000000..35cd437fdc
--- /dev/null
+++ b/doc/update/7.5-to-7.6.md
@@ -0,0 +1,114 @@
+# From 7.5 to 7.6
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-6-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-6-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.4.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+sudo apt-get install libkrb5-dev
+
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (7.5)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.4 to 7.5](7.4-to-7.5.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.6-to-7.7.md b/doc/update/7.6-to-7.7.md
new file mode 100644
index 0000000000..5924371315
--- /dev/null
+++ b/doc/update/7.6-to-7.7.md
@@ -0,0 +1,119 @@
+# From 7.6 to 7.7
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-7-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.4.2
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+sudo apt-get install libkrb5-dev
+
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-6-stable:config/gitlab.yml.example origin/7-7-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+### 8. GitHub settings (if applicable)
+
+If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
+only contains a root URL (ex. `https://gitlab.example.com/`)
+
+## Things went south? Revert to previous version (7.6)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.5 to 7.6](7.5-to-7.6.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.7-to-7.8.md b/doc/update/7.7-to-7.8.md
new file mode 100644
index 0000000000..46ca163c1b
--- /dev/null
+++ b/doc/update/7.7-to-7.8.md
@@ -0,0 +1,120 @@
+# From 7.7 to 7.8
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-8-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-8-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.5.4
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+sudo apt-get install libkrb5-dev
+
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-7-stable:config/gitlab.yml.example origin/7-8-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
+* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+### 8. GitHub settings (if applicable)
+
+If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
+only contains a root URL (ex. `https://gitlab.example.com/`)
+
+## Things went south? Revert to previous version (7.7)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.6 to 7.7](7.6-to-7.7.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.8-to-7.9.md b/doc/update/7.8-to-7.9.md
new file mode 100644
index 0000000000..28fd433e1c
--- /dev/null
+++ b/doc/update/7.8-to-7.9.md
@@ -0,0 +1,120 @@
+# From 7.8 to 7.9
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-9-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-9-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+sudo apt-get install nodejs
+
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-8-stable:config/gitlab.yml.example origin/7-9-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
+* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+### 8. GitHub settings (if applicable)
+
+If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
+only contains a root URL (ex. `https://gitlab.example.com/`)
+
+## Things went south? Revert to previous version (7.8)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.7 to 7.8](7.7-to-7.8.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/README.md b/doc/update/README.md
index 9a6f09b370..0472537eeb 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -1,4 +1,16 @@
-- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update)
-- [Upgrader](upgrader.md)
-- [Patch versions](patch_versions.md)
-- [MySQL to PostgreSQL](mysql_to_postgresql.md)
+Depending on the installation method and your GitLab version, there are multiple update guides. Choose one that fits your needs.
+
+## Omnibus Packages
+
+- [Omnibus update guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md) contains the steps needed to update a GitLab [package](https://about.gitlab.com/downloads/).
+
+## Installation from source
+
+- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab from source.
+- [The CE to EE update guides](https://gitlab.com/subscribers/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status.
+- [Upgrader](upgrader.md) is an automatic ruby script that performs the update for installations from source.
+- [Patch versions](patch_versions.md) guide includes the steps needed for a patch version, eg. 6.2.0 to 6.2.1.
+
+## Miscellaneous
+
+- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL.
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 219a3bb635..50941db25f 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -1,4 +1,5 @@
# Migrating GitLab from MySQL to Postgres
+*Make sure you view this [guide from the `master` branch](../../../master/doc/update/mysql_to_postgresql.md) for the most up to date instructions.*
If you are replacing MySQL with Postgres while keeping GitLab on the same server all you need to do is to export from MySQL, import into Postgres and rebuild the indexes as described below. If you are also moving GitLab to another server, or if you are switching to omnibus-gitlab, you may want to use a GitLab backup file. The second part of this documents explains the procedure to do this.
@@ -11,14 +12,19 @@ sudo service gitlab stop
# Update /home/git/gitlab/config/database.yml
-git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
+git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd mysql-postgresql-converter
-mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
+mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production -p
python db_converter.py databasename.mysql databasename.psql
-psql -f databasename.psql -d gitlabhq_production
+
+# Import the database dump as the application database user
+sudo -u git psql -f databasename.psql -d gitlabhq_production
# Rebuild indexes (see below)
+# Install gems for PostgreSQL (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
sudo service gitlab start
```
@@ -33,7 +39,7 @@ On non-omnibus installations (distributed using Git) we retrieve the index decla
```
# Clone the database converter on your Postgres-backed GitLab server
cd /tmp
-git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
+git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd /home/git/gitlab
@@ -54,7 +60,7 @@ On omnibus-gitlab we need to get the index declarations from a file called `sche
```
# Clone the database converter on your Postgres-backed GitLab server
cd /tmp
-/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
+/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd /tmp/mysql-postgresql-converter
# Download schema.rb.bundled if necessary
@@ -70,7 +76,7 @@ test -e /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb.bundled || sudo /
## Converting a GitLab backup file from MySQL to Postgres
**Note:** Please make sure to have Python 2.7.x (or higher) installed.
-GitLab backup files (_gitlab_backup.tar) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server.
+GitLab backup files (`_gitlab_backup.tar`) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server.
```
# Stop GitLab
@@ -89,10 +95,10 @@ sudo -u git -H mv tmp/backups/TIMESTAMP_gitlab_backup.tar tmp/backups/postgresql
# Create a separate database dump with PostgreSQL compatibility
cd tmp/backups/postgresql
-sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production
+sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p
# Clone the database converter
-sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
+sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
# Convert gitlabhq_production.mysql
sudo -u git -H mkdir db
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index c4a77d1280..e29ee2a7b3 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -1,4 +1,5 @@
# Universal update guide for patch versions
+*Make sure you view this [upgrade guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/patch_versions.md) from the `master` branch for the most up to date instructions.*
For example from 6.2.0 to 6.2.1, also see the [semantic versioning specification](http://semver.org/).
@@ -26,16 +27,14 @@ sudo -u git -H git checkout LATEST_TAG
Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`.
-### 3. Update gitlab-shell if it is not the latest version
+### 3. Update gitlab-shell to the corresponding version
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout LATEST_TAG
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
-Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`.
-
### 4. Install libs, migrations, etc.
```bash
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index e8379fcc31..f62a53d334 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -1,4 +1,5 @@
# GitLab Upgrader
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/upgrader.md) for the most up to date instructions.*
GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version.
@@ -10,6 +11,8 @@ If you have local changes to your GitLab repository the script will stash them a
**GitLab Upgrader is available only for GitLab version 6.4.2 or higher.**
+**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.**
+
## 0. Backup
cd /home/git/gitlab
@@ -21,7 +24,7 @@ If you have local changes to your GitLab repository the script will stash them a
## 2. Run GitLab upgrade tool
-Note: GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+Note: GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
# Starting with GitLab version 7.0 upgrader script has been moved to bin directory
cd /home/git/gitlab
@@ -43,28 +46,31 @@ Check if GitLab and its dependencies are configured correctly:
If all items are green, then congratulations upgrade is complete!
-## 5. Upgrade GitLab Shell (if needed)
+## 5. Upgrade GitLab Shell
-If the `gitlab:check` task reports an outdated version of `gitlab-shell` you should upgrade it.
-
-Upgrade it by running the commands below after replacing 1.9.4 with the correct version number:
+GitLab Shell might be outdated, running the commands below ensures you're using a compatible version:
```
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v1.9.4
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
## One line upgrade command
You've read through the entire guide and probably already did all the steps one by one.
-Here is a one line command with step 1 to 4 for the next time you upgrade:
+Here is a one line command with step 1 to 5 for the next time you upgrade:
```bash
-cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
+cd /home/git/gitlab; \
+ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
sudo service gitlab stop; \
if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \
+ cd /home/git/gitlab-shell; \
+ sudo -u git -H git fetch; \
+ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
+ cd /home/git/gitlab; \
sudo service gitlab start; \
sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 13c4de4301..851f50f5e9 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -16,24 +16,29 @@ Triggered when you push to the repository except when pushing tags.
```json
{
+ "object_kind": "push",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master",
"user_id": 4,
"user_name": "John Smith",
+ "user_email": "john@example.com",
"project_id": 15,
"repository": {
"name": "Diaspora",
- "url": "git@example.com:diaspora.git",
+ "url": "git@example.com:mike/diasporadiaspora.git",
"description": "",
- "homepage": "http://example.com/diaspora"
+ "homepage": "http://example.com/mike/diaspora",
+ "git_http_url":"http://example.com/mike/diaspora.git",
+ "git_ssh_url":"git@example.com:mike/diaspora.git",
+ "visibility_level":0
},
"commits": [
{
"id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"message": "Update Catalan translation to e38cb41.",
"timestamp": "2011-12-12T14:27:31+02:00",
- "url": "http://example.com/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"author": {
"name": "Jordi Mallach",
"email": "jordi@softcatala.org"
@@ -43,7 +48,7 @@ Triggered when you push to the repository except when pushing tags.
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"message": "fixed readme",
"timestamp": "2012-01-03T23:36:29+02:00",
- "url": "http://example.com/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"author": {
"name": "GitLab dev user",
"email": "gitlabdev@dv6700.(none)"
@@ -54,6 +59,35 @@ Triggered when you push to the repository except when pushing tags.
}
```
+## Tag events
+
+Triggered when you create (or delete) tags to the repository.
+
+**Request body:**
+
+```json
+{
+ "object_kind": "tag_push",
+ "ref": "refs/tags/v1.0.0",
+ "before": "0000000000000000000000000000000000000000",
+ "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
+ "user_id": 1,
+ "user_name": "John Smith",
+ "project_id": 1,
+ "repository": {
+ "name": "jsmith",
+ "url": "ssh://git@example.com/jsmith/example.git",
+ "description": "",
+ "homepage": "http://example.com/jsmith/example",
+ "git_http_url":"http://example.com/jsmith/example.git",
+ "git_ssh_url":"git@example.com:jsmith/example.git",
+ "visibility_level":0
+ },
+ "commits": [],
+ "total_commits_count": 0
+}
+```
+
## Issues events
Triggered when a new issue is created or an existing issue was updated/closed/reopened.
@@ -63,6 +97,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re
```json
{
"object_kind": "issue",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
"object_attributes": {
"id": 301,
"title": "New API: create/update/delete file",
@@ -92,6 +131,11 @@ Triggered when a new merge request is created or an existing merge request was u
```json
{
"object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
"object_attributes": {
"id": 99,
"target_branch": "master",
@@ -109,7 +153,33 @@ Triggered when a new merge request is created or an existing merge request was u
"merge_status": "unchecked",
"target_project_id": 14,
"iid": 1,
- "description": ""
+ "description": "",
+ "source": {
+ "name": "awesome_project",
+ "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
+ "http_url": "http://example.com/awesome_space/awesome_project.git",
+ "visibility_level": 20,
+ "namespace": "awesome_space"
+ },
+ "target": {
+ "name": "awesome_project",
+ "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
+ "http_url": "http://example.com/awesome_space/awesome_project.git",
+ "visibility_level": 20,
+ "namespace": "awesome_space"
+ },
+ "last_commit": {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ },
+ "url": "http://example.com/diaspora/merge_requests/1",
+ "action": "open"
}
}
```
@@ -124,12 +194,14 @@ Save the following file as `print_http_body.rb`.
```ruby
require 'webrick'
-server = WEBrick::HTTPServer.new(Port: ARGV.first)
+server = WEBrick::HTTPServer.new(:Port => ARGV.first)
server.mount_proc '/' do |req, res|
puts req.body
end
-trap 'INT' do server.shutdown end
+trap 'INT' do
+ server.shutdown
+end
server.start
```
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index b18f62a1fa..7e996dc47d 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -1,5 +1,15 @@
-- [Workflow](workflow.md)
+# Workflow
+
+- [Feature branch workflow](workflow.md)
+- [Project forking workflow](forking_workflow.md)
- [Project Features](project_features.md)
- [Authorization for merge requests](authorization_for_merge_requests.md)
- [Groups](groups.md)
- [Labels](labels.md)
+- [GitLab Flow](gitlab_flow.md)
+- [Notifications](notifications.md)
+- [Migrating from SVN to GitLab](migrating_from_svn.md)
+- [Project importing from GitHub to GitLab](import_projects_from_github.md)
+- [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md)
+- [Protected branches](protected_branches.md)
+- [Web Editor](web_editor.md)
diff --git a/doc/workflow/ci_mr.png b/doc/workflow/ci_mr.png
new file mode 100644
index 0000000000..a577356f8e
Binary files /dev/null and b/doc/workflow/ci_mr.png differ
diff --git a/doc/workflow/close_issue_mr.png b/doc/workflow/close_issue_mr.png
new file mode 100644
index 0000000000..a136d642e1
Binary files /dev/null and b/doc/workflow/close_issue_mr.png differ
diff --git a/doc/workflow/environment_branches.png b/doc/workflow/environment_branches.png
new file mode 100644
index 0000000000..ee893ced13
Binary files /dev/null and b/doc/workflow/environment_branches.png differ
diff --git a/doc/workflow/forking/branch_select.png b/doc/workflow/forking/branch_select.png
new file mode 100644
index 0000000000..275f64d113
Binary files /dev/null and b/doc/workflow/forking/branch_select.png differ
diff --git a/doc/workflow/forking/fork_button.png b/doc/workflow/forking/fork_button.png
new file mode 100644
index 0000000000..def4266476
Binary files /dev/null and b/doc/workflow/forking/fork_button.png differ
diff --git a/doc/workflow/forking/groups.png b/doc/workflow/forking/groups.png
new file mode 100644
index 0000000000..3ac64b3c8e
Binary files /dev/null and b/doc/workflow/forking/groups.png differ
diff --git a/doc/workflow/forking/merge_request.png b/doc/workflow/forking/merge_request.png
new file mode 100644
index 0000000000..2dc00ed08a
Binary files /dev/null and b/doc/workflow/forking/merge_request.png differ
diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md
new file mode 100644
index 0000000000..8edf7c6ab3
--- /dev/null
+++ b/doc/workflow/forking_workflow.md
@@ -0,0 +1,36 @@
+# Project forking workflow
+
+Forking a project to your own namespace is useful if you have no write access to the project you want to contribute
+to. If you do have write access or can request it we recommend working together in the same repository since it is simpler.
+See our **[GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)** article for more information about using
+branches to work together.
+
+## Creating a fork
+
+In order to create a fork of a project, all you need to do is click on the fork button located on the top right side
+of the screen, close to the project's URL and right next to the stars button.
+
+![Fork button](forking/fork_button.png)
+
+Once you do that you'll be presented with a screen where you can choose the namespace to fork to. Only namespaces
+(groups and your own namespace) where you have write access to, will be shown. Click on the namespace to create your
+fork there.
+
+![Groups view](forking/groups.png)
+
+After the forking is done, you can start working on the newly created repository. There you will have full
+[Owner](../permissions/permissions.md) access, so you can set it up as you please.
+
+## Merging upstream
+
+Once you are ready to send your code back to the main project, you need to create a merge request. Choose your forked
+project's main branch as the source and the original project's main branch as the destination and create the merge request.
+
+![Selecting branches](forking/branch_select.png)
+
+You can then assign the merge request to someone to have them review your changes. Upon pressing the 'Accept Merge Request'
+button, your changes will be added to the repository and branch you're merging into.
+
+![New merge request](forking/merge_request.png)
+
+
diff --git a/doc/workflow/four_stages.png b/doc/workflow/four_stages.png
new file mode 100644
index 0000000000..2f444fc6f7
Binary files /dev/null and b/doc/workflow/four_stages.png differ
diff --git a/doc/workflow/git_pull.png b/doc/workflow/git_pull.png
new file mode 100644
index 0000000000..7d47064eb1
Binary files /dev/null and b/doc/workflow/git_pull.png differ
diff --git a/doc/workflow/gitdashflow.png b/doc/workflow/gitdashflow.png
new file mode 100644
index 0000000000..f2f091dd10
Binary files /dev/null and b/doc/workflow/gitdashflow.png differ
diff --git a/doc/workflow/github_flow.png b/doc/workflow/github_flow.png
new file mode 100644
index 0000000000..88addb623e
Binary files /dev/null and b/doc/workflow/github_flow.png differ
diff --git a/doc/workflow/github_importer/importer.png b/doc/workflow/github_importer/importer.png
new file mode 100644
index 0000000000..5763671757
Binary files /dev/null and b/doc/workflow/github_importer/importer.png differ
diff --git a/doc/workflow/github_importer/new_project_page.png b/doc/workflow/github_importer/new_project_page.png
new file mode 100644
index 0000000000..002f22d81d
Binary files /dev/null and b/doc/workflow/github_importer/new_project_page.png differ
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
new file mode 100644
index 0000000000..0e87dc7421
--- /dev/null
+++ b/doc/workflow/gitlab_flow.md
@@ -0,0 +1,316 @@
+![GitLab Flow](gitlab_flow.png)
+
+## Introduction
+
+Version management with git makes branching and merging much easier than older versioning systems such as SVN.
+This allows a wide variety of branching strategies and workflows.
+Almost all of these are an improvement over the methods used before git.
+But many organizations end up with a workflow that is not clearly defined, overly complex or not integrated with issue tracking systems.
+Therefore we propose the GitLab flow as clearly defined set of best practices.
+It combines [feature driven development](http://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](http://martinfowler.com/bliki/FeatureBranch.html) with issue tracking.
+
+Organizations coming to git from other version control systems frequently find it hard to develop an effective workflow.
+This article describes the GitLab flow that integrates the git workflow with an issue tracking system.
+It offers a simple, transparent and effective way to work with git.
+
+![Four stages (working copy, index, local repo, remote repo) and three steps between them](four_stages.png)
+
+When converting to git you have to get used to the fact that there are three steps before a commit is shared with colleagues.
+Most version control systems have only step, committing from the working copy to a shared server.
+In git you add files from the working copy to the staging area. After that you commit them to the local repo.
+The third step is pushing to a shared remote repository.
+After getting used to these three steps the branching model becomes the challenge.
+
+![Multiple long running branches and merging in all directions](messy_flow.png)
+
+Since many organizations new to git have no conventions how to work with it, it can quickly become a mess.
+The biggest problem they run into is that many long running branches that each contain part of the changes are around.
+People have a hard time figuring out which branch they should develop on or deploy to production.
+Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html)
+We think there is still room for improvement and will detail a set of practices we call GitLab flow.
+
+## Git flow and its problems
+
+[![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
+
+Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
+It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
+The development happens on the develop branch, moves to a release branch and is finally merged into the master branch.
+Git flow is a well defined standard but its complexity introduces two problems.
+The first problem is that developers must use the develop branch and not master, master is reserved for code that is released to production.
+It is a convention to call your default branch master and to mostly branch from and merge to this.
+Since most tools automatically make the master branch the default one and display that one by default it is annoying to have to switch to another one.
+The second problem of git flow is the complexity introduced by the hotfix and release branches.
+These branches can be a good idea for some organizations but are overkill for the vast majority of them.
+Nowadays most organizations practice continuous delivery which means that your default branch can be deployed.
+This means that hotfix and release branches can be prevented including all the ceremony they introduce.
+An example of this ceremony is the merging back of release branches.
+Though specialized tools do exist to solve this, they require documentation and add complexity.
+Frequently developers make a mistake and for example changes are only merged into master and not into the develop branch.
+The root cause of these errors is that git flow is too complex for most of the use cases.
+And doing releases doesn't automatically mean also doing hotfixes.
+
+## GitHub flow as a simpler alternative
+
+![Master branch with feature branches merged in](github_flow.png)
+
+ In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html).
+This flow has only feature branches and a master branch.
+This is very simple and clean, many organizations have adopted it with great success.
+Atlassian recommends [a similar strategy](http://blogs.atlassian.com/2014/01/simple-git-workflow-simple/) although they rebase feature branches.
+Merging everything into the master branch and deploying often means you minimize the amount of code in 'inventory' which is in line with the lean and continuous delivery best practices.
+But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues.
+With GitLab flow we offer additional guidance for these questions.
+
+## Production branch with GitLab flow
+
+![Master branch and production branch with arrow that indicate deployments](production_branch.png)
+
+GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
+This is possible for SaaS applications but are many cases where this is not possible.
+One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation.
+Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
+In these cases you can make a production branch that reflects the deployed code.
+You can deploy a new version by merging in master to the production branch.
+If you need to know what code is in production you can just checkout the production branch to see.
+The approximate time of deployment is easily visible as the merge commit in the version control system.
+This time is pretty accurate if you automatically deploy your production branch.
+If you need a more exact time you can have your deployment script create a tag on each deployment.
+This flow prevents the overhead of releasing, tagging and merging that is common to git flow.
+
+## Environment branches with GitLab flow
+
+![Multiple branches with the code cascading from one to another](environment_branches.png)
+
+It might be a good idea to have an environment that is automatically updated to the master branch.
+Only in this case, the name of this environment might differ from the branch name.
+Suppose you have a staging environment, a pre-production environment and a production environment.
+In this case the master branch is deployed on staging. When someone wants to deploy to pre-production they create a merge request from the master branch to the pre-production branch.
+And going live with code happens by merging the pre-production branch into the production branch.
+This workflow where commits only flow downstream ensures that everything has been tested on all environments.
+If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch.
+If master is good to go (it should be if you a practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches.
+If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
+An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/).
+
+## Release branches with GitLab flow
+
+![Master and multiple release branches that vary in length with cherry-picks from master](release_branches.png)
+
+Only in case you need to release software to the outside world you need to work with release branches.
+In this case, each branch contains a minor version (2-3-stable, 2-4-stable, etc.).
+The stable branch uses master as a starting point and is created as late as possible.
+By branching as late as possible you minimize the time you have to apply bug fixes to multiple branches.
+After a release branch is announced, only serious bug fixes are included in the release branch.
+If possible these bug fixes are first merged into master and then cherry-picked into the release branch.
+This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases.
+This is called an 'upstream first' policy that is also practiced by [Google](http://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](http://www.redhat.com/about/news/archive/2013/5/a-community-for-using-openstack-with-red-hat-rdo).
+Every time a bug-fix is included in a release branch the patch version is raised (to comply with [Semantic Versioning](http://semver.org/)) by setting a new tag.
+Some projects also have a stable branch that points to the same commit as the latest released branch.
+In this flow it is not common to have a production branch (or git flow master branch).
+
+## Merge/pull requests with GitLab flow
+
+![Merge request with line comments](mr_inline_comments.png)
+
+Merge or pull requests are created in a git management application and ask an assigned person to merge two branches.
+Tools such as GitHub and Bitbucket choose the name pull request since the first manual action would be to pull the feature branch.
+Tools such as GitLab and Gitorious choose the name merge request since that is the final action that is requested of the assignee.
+In this article we'll refer to them as merge requests.
+
+If you work on a feature branch for more than a few hours it is good to share the intermediate result with the rest of the team.
+This can be done by creating a merge request without assigning it to anyone, instead you mention people in the description or a comment (/cc @mark @susan).
+This means it is not ready to be merged but feedback is welcome.
+Your team members can comment on the merge request in general or on specific lines with line comments.
+The merge requests serves as a code review tool and no separate tools such as Gerrit and reviewboard should be needed.
+If the review reveals shortcomings anyone can commit and push a fix.
+Commonly the person to do this is the creator of the merge/pull request.
+The diff in the merge/pull requests automatically updates when new commits are pushed on the branch.
+
+When you feel comfortable with it to be merged you assign it to the person that knows most about the codebase you are changing and mention any other people you would like feedback from.
+There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
+If the assigned person does not feel comfortable they can close the merge request without merging.
+
+In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md).
+So if you want to merge it into a protected branch you assign it to someone with master authorizations.
+
+## Issues with GitLab flow
+
+![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png)
+
+GitLab flow is a way to make the relation between the code and the issue tracker more transparent.
+
+Any significant change to the code should start with an issue where the goal is described.
+Having a reason for every code change is important to inform everyone on the team and to help people keep the scope of a feature branch small.
+In GitLab each change to the codebase starts with an issue in the issue tracking system.
+If there is no issue yet it should be created first provided there is significant work involved (more than 1 hour).
+For many organizations this will be natural since the issue will have to be estimated for the sprint.
+Issue titles should describe the desired state of the system, e.g. "As an administrator I want to remove users without receiving an error" instead of "Admin can't remove users.".
+
+When you are ready to code you start a branch for the issue from the master branch.
+The name of this branch should start with the issue number, for example '15-require-a-password-to-change-it'.
+
+When you are done or want to discuss the code you open a merge request.
+This is an online place to discuss the change and review the code.
+Creating a branch is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
+If you create the merge request but do not assign it to anyone it is a 'work-in-process' merge request.
+These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
+
+When the author thinks the code is ready the merge request is assigned to reviewer.
+The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
+In this case the code is merged and a merge commit is generated that makes this event easily visible later on.
+Merge requests always create a merge commit even when the commit could be added without one.
+This merge strategy is called 'no fast-forward' in git.
+After the merge the feature branch is deleted since it is no longer needed, in GitLab this deletion is an option when merging.
+
+Suppose that a branch is merged but a problem occurs and the issue is reopened.
+In this case it is no problem to reuse the same branch name since it was deleted when the branch was merged.
+At any time there is at most one branch for every issue.
+It is possible that one feature branch solves more than one issue.
+
+## Linking and closing issues from merge requests
+
+![Merge request showing the linked issues that will be closed](close_issue_mr.png)
+
+Linking to the issue can happen by mentioning them from commit messages (fixes #14, closes #67, etc.) or from the merge request description.
+In GitLab this creates a comment in the issue that the merge requests mentions the issue.
+And the merge request shows the linked issues.
+These issues are closed once code is merged into the default branch.
+
+If you only want to make the reference without closing the issue you can also just mention it: "Duck typing is preferred. #12".
+
+If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue.
+
+## Squashing commits with rebase
+
+![Vim screen showing the rebase view](rebase.png)
+
+With git you can use an interactive rebase (rebase -i) to squash multiple commits into one and reorder them.
+This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
+However you should never rebase commits you have pushed to a remote server.
+Somebody can have referred to the commits or cherry-picked them.
+When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
+If you do that the same change will be known under multiple identifiers and this can cause much confusion.
+If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
+
+People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on.
+This will lead to many commits per change which makes the history harder to understand.
+But the advantages of having stable identifiers outweigh this drawback.
+And to understand a change in context one can always look at the merge commit that groups all the commits together when the code is merged into the master branch.
+
+After you merge multiple commits from a feature branch into the master branch this is harder to undo.
+If you would have squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed.
+Fortunately [reverting a merge made some time ago](http://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git.
+This however, requires having specific merge commits for the commits your want to revert.
+If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise.
+
+Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option.
+Git management software will always create a merge commit when you accept a merge request.
+
+## Do not order commits with rebase
+
+![List of sequential merge commits](merge_commits.png)
+
+With git you can also rebase your feature branch commits to order them after the commits on the master branch.
+This prevents creating a merge commit when merging master into your feature branch and creates a nice linear history.
+However, just like with squashing you should never rebase commits you have pushed to a remote server.
+This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend.
+When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](http://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/).
+You can reuse recorded resolutions (rerere) sometimes, but with without rebasing you only have to solve the conflicts one time and you’re set.
+There has to be a better way to avoid many merge commits.
+
+The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
+We'll discuss the three reasons to merge in master: leveraging code, solving merge conflicts and long running branches.
+If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit.
+If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this.
+You should aim to prevent merge conflicts where they are likely to occur.
+One example is the CHANGELOG file where each significant change in the codebase is documented under a version header.
+Instead of everyone adding their change at the bottom of the list for the current version it is better to randomly insert it in the current list for that version.
+This it is likely that multiple feature branches that add to the CHANGELOG can be merged before a conflict occurs.
+The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project.
+Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
+At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
+That's continuous building, and a Good Thing, but there's no integration, so it's not CI.".
+The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work.
+If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
+As for the long running branches that take more than one day there are two strategies.
+In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time.
+In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release.
+This strategy is [advocated by Linus Torvalds](https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html) because the state of the code at these points is better known.
+
+In conclusion, we can say that you should try to prevent merge commits, but not eliminate them.
+Your codebase should be clean but your history should represent what actually happened.
+Developing software happen in small messy steps and it is OK to have your history reflect this.
+You can use tools to view the network graphs of commits and understand the messy history that created your code.
+If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
+
+## Voting on merge requests
+
+![Voting slider in GitLab](voting_slider.png)
+
+It is common to voice approval or disapproval by using +1 or -1 emoticons.
+In GitLab the +1 and -1 are aggregated and shown at the top of the merge request.
+As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet.
+
+## Pushing and removing branches
+
+![Remove checkbox for branch in merge requests](remove_checkbox.png)
+
+We recommend that people push their feature branches frequently, even when they are not ready for review yet.
+By doing this you prevent team members from accidentally starting to work on the same issue.
+Of course this situation should already be prevented by assigning someone to the issue in the issue tracking software.
+However sometimes one of the two parties forgets to assign someone in the issue tracking software.
+After a branch is merged it should be removed from the source control software.
+In GitLab and similar systems this is an option when merging.
+This ensures that the branch overview in the repository management software shows only work in progress.
+This also ensures that when someone reopens the issue a new branch with the same name can be used without problem.
+When you reopen an issue you need to create a new merge request.
+
+## Committing often and with the right message
+
+![Good and bad commit message](good_commit.png)
+
+We recommend to commit early and often.
+Each time you have a functioning set of tests and code a commit can be made.
+The advantage is that when an extension or refactor goes wrong it is easy to revert to a working version.
+This is quite a change for programmers that used SVN before, they used to commit when their work was ready to share.
+The trick is to use the merge/pull request with multiple commits when your work is ready to share.
+The commit message should reflect your intention, not the contents of the commit.
+The contents of the commit can be easily seen anyway, the question is why you did it.
+An example of a good commit message is: "Combine templates to dry up the user views.".
+Some words that are bad commit messages because they don't contain munch information are: change, improve and refactor.
+The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number.
+To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+
+## Testing before merging
+
+![Merge requests showing the test states, red, yellow and green](ci_mr.png)
+
+In old workflows the Continuous Integration (CI) server commonly ran tests on the master branch only.
+Developers had to ensure their code did not break the master branch.
+When using GitLab flow developers create their branches from this master branch so it is essential it is green.
+Therefore each merge request must be tested before it is accepted.
+CI software like Travis and GitLab CI show the build results right in the merge request itself to make this easy.
+One drawback is that they are testing the feature branch itself and not the merged result.
+What one can do to improve this is to test the merged result itself.
+The problem is that the merge result changes every time something is merged into master.
+Retesting on every commit to master is computationally expensive and means you are more frequently waiting for test results.
+If there are no merge conflicts and the feature branches are short lived the risk is acceptable.
+If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
+If you have long lived feature branches that last for more than a few days you should make your issues smaller.
+
+## Merging in other code
+
+![Shell output showing git pull output](git_pull.png)
+
+When initiating a feature branch, always start with an up to date master to branch off from.
+If you know beforehand that your work absolutely depends on another branch you can also branch from there.
+If you need to merge in another branch after starting explain the reason in the merge commit.
+If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch.
+Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](http://lwn.net/Articles/328438/).
+Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history.
+
+### References
+
+- [Sketch file](https://www.dropbox.com/s/58dvsj5votbwrzv/git_flows.sketch?dl=0) with vectors of images in this article
+- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/)
diff --git a/doc/workflow/gitlab_flow.png b/doc/workflow/gitlab_flow.png
new file mode 100644
index 0000000000..1ea191a672
Binary files /dev/null and b/doc/workflow/gitlab_flow.png differ
diff --git a/doc/workflow/gitlab_importer/importer.png b/doc/workflow/gitlab_importer/importer.png
new file mode 100644
index 0000000000..d2a286d8ca
Binary files /dev/null and b/doc/workflow/gitlab_importer/importer.png differ
diff --git a/doc/workflow/gitlab_importer/new_project_page.png b/doc/workflow/gitlab_importer/new_project_page.png
new file mode 100644
index 0000000000..5e239208e1
Binary files /dev/null and b/doc/workflow/gitlab_importer/new_project_page.png differ
diff --git a/doc/workflow/good_commit.png b/doc/workflow/good_commit.png
new file mode 100644
index 0000000000..3737a02664
Binary files /dev/null and b/doc/workflow/good_commit.png differ
diff --git a/doc/workflow/import_projects_from_github.md b/doc/workflow/import_projects_from_github.md
new file mode 100644
index 0000000000..8644b4ffc7
--- /dev/null
+++ b/doc/workflow/import_projects_from_github.md
@@ -0,0 +1,13 @@
+# Project importing from GitHub to GitLab
+
+You can import your existing GitHub projects to GitLab. But keep in mind that it is possible only if
+GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
+To get to the importer page you need to go to "New project" page.
+
+![New project page](github_importer/new_project_page.png)
+
+Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
+
+![Importer page](github_importer/importer.png)
+
+To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data.
\ No newline at end of file
diff --git a/doc/workflow/import_projects_from_gitlab_com.md b/doc/workflow/import_projects_from_gitlab_com.md
new file mode 100644
index 0000000000..f4c4e955d4
--- /dev/null
+++ b/doc/workflow/import_projects_from_gitlab_com.md
@@ -0,0 +1,18 @@
+# Project importing from GitLab.com to your private GitLab instance
+
+You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
+GitLab support is enabled on your GitLab instance.
+You can read more about Gitlab support [here](http://doc.gitlab.com/ce/integration/gitlab.html)
+To get to the importer page you need to go to "New project" page.
+
+![New project page](gitlab_importer/new_project_page.png)
+
+Click on the "Import projects from Gitlab.com" link and you will be redirected to GitLab.com
+for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
+
+
+![Importer page](gitlab_importer/importer.png)
+
+
+To import a project, you can simple click "Import". The importer will import your repository and issues.
+Once the importer is done, a new GitLab project will be created with your imported data.
\ No newline at end of file
diff --git a/doc/workflow/merge_commits.png b/doc/workflow/merge_commits.png
new file mode 100644
index 0000000000..757b589d0d
Binary files /dev/null and b/doc/workflow/merge_commits.png differ
diff --git a/doc/workflow/merge_request.png b/doc/workflow/merge_request.png
new file mode 100644
index 0000000000..fde3ff5c85
Binary files /dev/null and b/doc/workflow/merge_request.png differ
diff --git a/doc/workflow/messy_flow.png b/doc/workflow/messy_flow.png
new file mode 100644
index 0000000000..1addb95ca5
Binary files /dev/null and b/doc/workflow/messy_flow.png differ
diff --git a/doc/workflow/migrating_from_svn.md b/doc/workflow/migrating_from_svn.md
new file mode 100644
index 0000000000..485db4834e
--- /dev/null
+++ b/doc/workflow/migrating_from_svn.md
@@ -0,0 +1,17 @@
+# Migrating from SVN to GitLab
+
+SVN stands for Subversion and is a version control system (VCS).
+Git is a distributed version control system.
+
+There are some major differences between the two, for more information consult your favorite search engine.
+
+Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at
+[git documentation pages](http://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion).
+
+Apart from the [official git documentation](http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also
+user created step by step guide for migrating from SVN to GitLab.
+
+[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca).
+
+## Contribute to this guide
+We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems.
diff --git a/doc/workflow/mr_inline_comments.png b/doc/workflow/mr_inline_comments.png
new file mode 100644
index 0000000000..e851b95bce
Binary files /dev/null and b/doc/workflow/mr_inline_comments.png differ
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
new file mode 100644
index 0000000000..17215de677
--- /dev/null
+++ b/doc/workflow/notifications.md
@@ -0,0 +1,71 @@
+# GitLab Notifications
+
+GitLab has notifications system in place to notify a user of events important for the workflow.
+
+## Notification settings
+
+Under user profile page you can find the notification settings.
+
+![notification settings](notifications/settings.png)
+
+Notification settings are divided into three groups:
+
+* Global Settings
+* Group Settings
+* Project Settings
+
+Each of these settings have levels of notification:
+
+* Disabled - turns off notifications
+* Participating - receive notifications from related resources
+* Watch - receive notifications from projects or groups user is a member of
+* Global - notifications as set at the global settings
+
+#### Global Settings
+
+Global Settings are at the bottom of the hierarchy.
+Any setting set here will be overridden by a setting at the group or a project level.
+
+Group or Project settings can use `global` notification setting which will then use
+anything that is set at Global Settings.
+
+#### Group Settings
+
+Group Settings are taking precedence over Global Settings but are on a level below Project Settings.
+This means that you can set a different level of notifications per group while still being able
+to have a finer level setting per project.
+Organization like this is suitable for users that belong to different groups but don't have the
+same need for being notified for every group they are member of.
+
+#### Project Settings
+
+Project Settings are at the top level and any setting placed at this level will take precedence of any
+other setting.
+This is suitable for users that have different needs for notifications per project basis.
+
+## Notification events
+
+Below is the table of events users can be notified of:
+
+| Event | Sent to | Settings level |
+|------------------------------|-------------------------------------------------------------------|------------------------------|
+| New SSH key added | User | Security email, always sent. |
+| New email added | User | Security email, always sent. |
+| New user created | User | Sent on user creation, except for omniauth (LDAP)|
+| New issue created | Issue assignee [1], project members [2] | [1] not disabled, [2] higher than participating |
+| User added to project | User | Sent when user is added to project |
+| Project access level changed | User | Sent when user project access level is changed |
+| User added to group | User | Sent when user is added to group |
+| Project moved | Project members [1] | [1] not disabled |
+| Group access level changed | User | Sent when user group access level is changed |
+| Close issue | Issue author [1], issue assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| Reassign issue | New issue assignee [1], old issue assignee [2] | [1] [2] not disabled |
+| Reopen issue | Project members [1] | [1] higher than participating |
+| New merge request | MR assignee [1] | [1] not disabled |
+| Reassign merge request | New MR assignee [1], old MR assignee [2] | [1] [2] not disabled |
+| Close merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| Reopen merge request | Project members [1] | [1] higher than participating |
+| Merge merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+
+
diff --git a/doc/workflow/notifications/settings.png b/doc/workflow/notifications/settings.png
new file mode 100644
index 0000000000..e5b50ee249
Binary files /dev/null and b/doc/workflow/notifications/settings.png differ
diff --git a/doc/workflow/production_branch.png b/doc/workflow/production_branch.png
new file mode 100644
index 0000000000..33fb26dd62
Binary files /dev/null and b/doc/workflow/production_branch.png differ
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
new file mode 100644
index 0000000000..805f7f8d35
--- /dev/null
+++ b/doc/workflow/protected_branches.md
@@ -0,0 +1,33 @@
+# Protected branches
+
+Permission in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
+
+To prevent people from messing with history or pushing code without review, we've created protected branches.
+
+A protected branch does three simple things:
+
+* it prevents pushes from everybody except users with Master permission
+* it prevents anyone from force pushing to the branch
+* it prevents anyone from deleting the branch
+
+You can make any branch a protected branch. GitLab makes the master branch a protected branch by default.
+
+To protect a branch, user needs to have at least a Master permission level, see [permissions document](permissions/permissions.md).
+
+![protected branches page](protected_branches/protected_branches1.png)
+
+Navigate to project settings page and select `protected branches`. From the `Branch` dropdown menu select the branch you want to protect.
+
+Some workflows, like [GitLab workflow](gitlab_flow.md), require all users with write access to submit a Merge request in order to get the code into a protected branch.
+
+Since Masters and Owners can already push to protected branches, that means Developers cannot push to protected branch and need to submit a Merge request.
+
+However, there are workflows where that is not needed and only protecting from force pushes and branch removal is useful.
+
+For those workflows, you can allow everyone with write access to push to a protected branch by selecting `Developers can push` check box.
+
+On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box.
+
+![Developers can push](protected_branches/protected_branches2.png)
+
+
diff --git a/doc/workflow/protected_branches/protected_branches1.png b/doc/workflow/protected_branches/protected_branches1.png
new file mode 100644
index 0000000000..5c2a3de5f7
Binary files /dev/null and b/doc/workflow/protected_branches/protected_branches1.png differ
diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png
new file mode 100644
index 0000000000..2dca354136
Binary files /dev/null and b/doc/workflow/protected_branches/protected_branches2.png differ
diff --git a/doc/workflow/rebase.png b/doc/workflow/rebase.png
new file mode 100644
index 0000000000..ef82c83475
Binary files /dev/null and b/doc/workflow/rebase.png differ
diff --git a/doc/workflow/release_branches.png b/doc/workflow/release_branches.png
new file mode 100644
index 0000000000..da7ae53413
Binary files /dev/null and b/doc/workflow/release_branches.png differ
diff --git a/doc/workflow/remove_checkbox.png b/doc/workflow/remove_checkbox.png
new file mode 100644
index 0000000000..3e247d3815
Binary files /dev/null and b/doc/workflow/remove_checkbox.png differ
diff --git a/doc/workflow/voting_slider.png b/doc/workflow/voting_slider.png
new file mode 100644
index 0000000000..4c660ef959
Binary files /dev/null and b/doc/workflow/voting_slider.png differ
diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md
new file mode 100644
index 0000000000..7fc8f96b9e
--- /dev/null
+++ b/doc/workflow/web_editor.md
@@ -0,0 +1,26 @@
+# GitLab Web Editor
+
+In GitLab you can create new files and edit existing files using our web editor.
+This is especially useful if you don't have access to a command line or you just want to do a quick fix.
+You can easily access the web editor, depending on the context.
+Let's start from newly created project.
+
+Click on `Add a file`
+to create the first file and open it in the web editor.
+
+![web editor 1](web_editor/empty_project.png)
+
+Fill in a file name, some content, a commit message, branch name and press the commit button.
+The file will be saved to the repository.
+
+![web editor 2](web_editor/new_file.png)
+
+You can edit any text file in a repository by pressing the edit button, when
+viewing the file.
+
+![web editor 3](web_editor/show_file.png)
+
+Editing a file is almost the same as creating a new file,
+with as addition the ability to preview your changes in a separate tab. Also you can save your change to another branch by filling out field `branch`
+
+![web editor 3](web_editor/edit_file.png)
diff --git a/doc/workflow/web_editor/edit_file.png b/doc/workflow/web_editor/edit_file.png
new file mode 100644
index 0000000000..f480c69ac3
Binary files /dev/null and b/doc/workflow/web_editor/edit_file.png differ
diff --git a/doc/workflow/web_editor/empty_project.png b/doc/workflow/web_editor/empty_project.png
new file mode 100644
index 0000000000..6a049f6bea
Binary files /dev/null and b/doc/workflow/web_editor/empty_project.png differ
diff --git a/doc/workflow/web_editor/new_file.png b/doc/workflow/web_editor/new_file.png
new file mode 100644
index 0000000000..55ebd9e025
Binary files /dev/null and b/doc/workflow/web_editor/new_file.png differ
diff --git a/doc/workflow/web_editor/show_file.png b/doc/workflow/web_editor/show_file.png
new file mode 100644
index 0000000000..9cafcb5510
Binary files /dev/null and b/doc/workflow/web_editor/show_file.png differ
diff --git a/doc/workflow/workflow.md b/doc/workflow/workflow.md
index ab29cfb670..f70e41df84 100644
--- a/doc/workflow/workflow.md
+++ b/doc/workflow/workflow.md
@@ -1,4 +1,4 @@
-# Workflow
+# Feature branch workflow
1. Clone project:
diff --git a/docker/.dockerignore b/docker/.dockerignore
new file mode 100644
index 0000000000..dd449725e1
--- /dev/null
+++ b/docker/.dockerignore
@@ -0,0 +1 @@
+*.md
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000000..bb25bb677c
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,33 @@
+FROM ubuntu:14.04
+
+# Install required packages
+RUN apt-get update -q \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -qy --no-install-recommends \
+ ca-certificates \
+ openssh-server \
+ wget
+
+# Download & Install GitLab
+# If the Omnibus package version below is outdated please contribute a merge request to update it.
+# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
+RUN TMP_FILE=$(mktemp); \
+ wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.2-omnibus-1_amd64.deb \
+ && dpkg -i $TMP_FILE \
+ && rm -f $TMP_FILE
+
+# Manage SSHD through runit
+RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
+ && mkfifo /opt/gitlab/sv/sshd/supervise/ok \
+ && printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
+ && chmod a+x /opt/gitlab/sv/sshd/run \
+ && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
+ && mkdir -p /var/run/sshd
+
+# Expose web & ssh
+EXPOSE 80 22
+
+# Copy assets
+COPY assets/wrapper /usr/local/bin/
+
+# Wrapper to handle signal, trigger runit and reconfigure GitLab
+CMD ["/usr/local/bin/wrapper"]
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000000..b7e8b0db7e
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,88 @@
+What is GitLab?
+===============
+
+GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. A subscription gives you access to our support team and to GitLab Enterprise Edition that contains extra features aimed at larger organizations.
+
+
+
+![GitLab Logo](https://gitlab.com/uploads/appearance/logo/1/brand_logo-c37eb221b456bb4b472cc1084480991f.png)
+
+
+How to use these images
+======================
+
+At this moment GitLab doesn't have official Docker images. For convinience we will use suffix _xy where xy is current version of GitLab.
+Build your own based on the Omnibus packages with the following commands (it assumes you're in the GitLab repo root directory):
+
+```bash
+sudo docker build --tag gitlab_data_image docker/data/
+sudo docker build --tag gitlab_app_image_xy docker/
+```
+
+We assume using a data volume container, this will simplify migrations and backups.
+This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it.
+
+The directories on data container are:
+
+- `/var/opt/gitlab` for application data
+- `/var/log/gitlab` for logs
+- `/etc/gitlab` for configuration
+
+Create the data container with:
+
+```bash
+sudo docker run --name gitlab_data gitlab_data_image /bin/true
+```
+
+After creating data container run GitLab container:
+
+```bash
+sudo docker run --detach --name gitlab_app_xy --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_app_image_xy
+```
+
+It might take a while before the docker container is responding to queries. You can follow the configuration process with `sudo docker logs -f gitlab_app_xy`.
+
+You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker).
+You can login with username `root` and password `5iveL!fe`.
+Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`.
+
+
+How to configure GitLab
+========================
+
+This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
+
+To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor:
+
+```bash
+sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu
+vi /etc/gitlab/gitlab.rb
+```
+
+**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab.
+
+You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
+
+How to upgrade GitLab
+========================
+
+To updgrade GitLab to new versions, stop running container, create new docker image and container from that image.
+
+It Assumes that you're upgrading from 7.8 to 7.9 and you're in the updated GitLab repo root directory:
+
+```bash
+sudo docker stop gitlab_app_78
+sudo docker build --tag gitlab_app_image_79 docker/
+sudo docker run --detach --name gitlab_app_79 --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_app_image_79
+```
+
+On the first run GitLab will reconfigure and update itself. If everything runs OK don't forget to cleanup old container and image:
+
+```bash
+sudo docker rm gitlab_app_78
+sudo docker rmi gitlab_app_image_78
+```
+
+Troubleshooting
+=========================
+Please see the [troubleshooting](troubleshooting.md) file in this directory.
diff --git a/docker/assets/wrapper b/docker/assets/wrapper
new file mode 100755
index 0000000000..9e6e7a0590
--- /dev/null
+++ b/docker/assets/wrapper
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+function sigterm_handler() {
+ echo "SIGTERM signal received, try to gracefully shutdown all services..."
+ gitlab-ctl stop
+}
+
+trap "sigterm_handler; exit" TERM
+
+function entrypoint() {
+ # Default is to run runit and reconfigure GitLab
+ gitlab-ctl reconfigure &
+ /opt/gitlab/embedded/bin/runsvdir-start &
+ wait
+}
+
+entrypoint
diff --git a/docker/data/Dockerfile b/docker/data/Dockerfile
new file mode 100644
index 0000000000..ea0175c4aa
--- /dev/null
+++ b/docker/data/Dockerfile
@@ -0,0 +1,8 @@
+FROM busybox
+
+# Declare volumes
+VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"]
+# Copy assets
+COPY assets/gitlab.rb /etc/gitlab/
+
+CMD /bin/sh
diff --git a/docker/data/assets/gitlab.rb b/docker/data/assets/gitlab.rb
new file mode 100644
index 0000000000..7fddf309c0
--- /dev/null
+++ b/docker/data/assets/gitlab.rb
@@ -0,0 +1,37 @@
+# External URL should be your Docker instance.
+# By default, this example is the "standard" boot2docker IP.
+# Always use port 80 here to force the internal nginx to bind port 80,
+# even if you intend to use another port in Docker.
+external_url "http://192.168.59.103/"
+
+# Prevent Postgres from trying to allocate 25% of total memory
+postgresql['shared_buffers'] = '1MB'
+
+# Configure GitLab to redirect PostgreSQL logs to the data volume
+postgresql['log_directory'] = '/var/log/gitlab/postgresql'
+
+# Some configuration of GitLab
+# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
+gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
+gitlab_rails['gitlab_support_email'] = 'support@example.com'
+gitlab_rails['time_zone'] = 'Europe/Paris'
+
+# SMTP settings
+# You must use an external server, the Docker container does not install an SMTP server
+gitlab_rails['smtp_enable'] = true
+gitlab_rails['smtp_address'] = "smtp.example.com"
+gitlab_rails['smtp_port'] = 587
+gitlab_rails['smtp_user_name'] = "user"
+gitlab_rails['smtp_password'] = "password"
+gitlab_rails['smtp_domain'] = "example.com"
+gitlab_rails['smtp_authentication'] = "plain"
+gitlab_rails['smtp_enable_starttls_auto'] = true
+
+# Enable LDAP authentication
+# gitlab_rails['ldap_enabled'] = true
+# gitlab_rails['ldap_host'] = 'ldap.example.com'
+# gitlab_rails['ldap_port'] = 389
+# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
+# gitlab_rails['ldap_allow_username_or_email_login'] = false
+# gitlab_rails['ldap_uid'] = 'uid'
+# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md
new file mode 100644
index 0000000000..b1b70de599
--- /dev/null
+++ b/docker/troubleshooting.md
@@ -0,0 +1,63 @@
+# Troubleshooting
+
+This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245
+But it might contain useful commands for other cases as well.
+
+The configuration to add the postgres log in vim is:
+postgresql['log_directory'] = '/var/log/gitlab/postgresql'
+
+# Commands
+
+```bash
+sudo docker build --tag gitlab_image docker/
+
+sudo docker rm -f gitlab_app
+sudo docker rm -f gitlab_data
+
+sudo docker run --name gitlab_data gitlab_image /bin/true
+
+sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb
+
+sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb
+```
+
+# Interactively
+
+```bash
+# First start a GitLab container without starting GitLab
+# This is almost the same as starting the GitLab container except:
+# - we run interactively (-t -i)
+# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
+# - we choose another startup command (bash)
+sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash
+
+# Configure GitLab to redirect PostgreSQL logs
+echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
+
+# Prevent Postgres from allocating 25% of total memory
+echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb
+
+# You can now start GitLab manually from Bash (in the background)
+# Maybe the command below is still missing something to run in the background
+gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start &
+
+# Inspect PostgreSQL config
+cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
+
+# And tail the logs (PostgreSQL log may not exist immediately)
+tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current
+
+# And get the memory
+cat /proc/meminfo
+head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall
+free -m
+
+```
diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature
index b28e16f0d6..5de07e90e2 100644
--- a/features/admin/active_tab.feature
+++ b/features/admin/active_tab.feature
@@ -1,5 +1,5 @@
@admin
-Feature: Admin active tab
+Feature: Admin Active Tab
Background:
Given I sign in as an admin
diff --git a/features/admin/applications.feature b/features/admin/applications.feature
new file mode 100644
index 0000000000..2a00e1666c
--- /dev/null
+++ b/features/admin/applications.feature
@@ -0,0 +1,18 @@
+@admin
+Feature: Admin Applications
+ Background:
+ Given I sign in as an admin
+ And I visit applications page
+
+ Scenario: I can manage application
+ Then I click on new application button
+ And I should see application form
+ Then I fill application form out and submit
+ And I see application
+ Then I click edit
+ And I see edit application form
+ Then I change name of application and submit
+ And I see that application was changed
+ Then I visit applications page
+ And I click to remove application
+ Then I see that application is removed
\ No newline at end of file
diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature
new file mode 100644
index 0000000000..9df47eb51f
--- /dev/null
+++ b/features/admin/deploy_keys.feature
@@ -0,0 +1,21 @@
+@admin
+Feature: Admin Deploy Keys
+ Background:
+ Given I sign in as an admin
+ And there are public deploy keys in system
+
+ Scenario: Deploy Keys list
+ When I visit admin deploy keys page
+ Then I should see all public deploy keys
+
+ Scenario: Deploy Keys show
+ When I visit admin deploy keys page
+ And I click on first deploy key
+ Then I should see deploy key details
+
+ Scenario: Deploy Keys new
+ When I visit admin deploy keys page
+ And I click 'New Deploy Key'
+ And I submit new deploy key
+ Then I should be on admin deploy keys page
+ And I should see newly created deploy key
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index 1a465c1be5..aa365a6ea1 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -20,3 +20,10 @@ Feature: Admin Groups
When I visit admin group page
When I select user "John Doe" from user list as "Reporter"
Then I should see "John Doe" in team list in every project as "Reporter"
+
+ @javascript
+ Scenario: Remove user from group
+ Given we have user "John Doe" in group
+ When I visit admin group page
+ And I remove user "John Doe" from group
+ Then I should not see "John Doe" in team list
diff --git a/features/admin/settings.feature b/features/admin/settings.feature
new file mode 100644
index 0000000000..52e47307b2
--- /dev/null
+++ b/features/admin/settings.feature
@@ -0,0 +1,16 @@
+@admin
+Feature: Admin Settings
+ Background:
+ Given I sign in as an admin
+ And I visit admin settings page
+
+ Scenario: Change application settings
+ When I modify settings and save form
+ Then I should see application settings saved
+
+ Scenario: Change Slack Service Template settings
+ When I click on "Service Templates"
+ And I click on "Slack" service
+ Then I check all events and submit form
+ And I should see service template settings saved
+ And I should see all checkboxes checked
diff --git a/features/admin/users.feature b/features/admin/users.feature
index d8c1288e5f..1a8720dd77 100644
--- a/features/admin/users.feature
+++ b/features/admin/users.feature
@@ -16,6 +16,12 @@ Feature: Admin Users
Then See username error message
And Not changed form action url
+ Scenario: Show user attributes
+ Given user "Mike" with groups and projects
+ Given I visit admin users page
+ And click on "Mike" link
+ Then I should see user "Mike" details
+
Scenario: Edit my user attributes
Given I visit admin users page
And click edit on my user
@@ -29,3 +35,13 @@ Feature: Admin Users
And I see the secondary email
When I click remove secondary email
Then I should not see secondary email anymore
+
+ Scenario: Show user keys
+ Given user "Pete" with ssh keys
+ And I visit admin users page
+ And click on user "Pete"
+ Then I should see key list
+ And I click on the key title
+ Then I should see key details
+ And I click on remove key
+ Then I should see the key removed
diff --git a/features/dashboard/active_tab.feature b/features/dashboard/active_tab.feature
index 22dfa2f784..08b87808f3 100644
--- a/features/dashboard/active_tab.feature
+++ b/features/dashboard/active_tab.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Dashboard active tab
+Feature: Dashboard Active Tab
Background:
Given I sign in as a user
diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature
index e23238d225..69b3a77644 100644
--- a/features/dashboard/archived_projects.feature
+++ b/features/dashboard/archived_projects.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Dashboard with archived projects
+Feature: Dashboard Archived Projects
Background:
Given I sign in as a user
And I own project "Shop"
@@ -10,8 +10,3 @@ Feature: Dashboard with archived projects
Scenario: I should see non-archived projects on dashboard
Then I should see "Shop" project link
And I should not see "Forum" project link
-
- Scenario: I should see all projects on projects page
- And I visit dashboard projects page
- Then I should see "Shop" project link
- And I should see "Forum" project link
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index bebaa78e46..1959d32708 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -27,11 +27,11 @@ Feature: Dashboard
Scenario: I should see User joined Project event
Given user with name "John Doe" joined project "Shop"
When I visit dashboard page
- Then I should see "John Doe joined project at Shop" event
+ Then I should see "John Doe joined project Shop" event
@javascript
Scenario: I should see User left Project event
Given user with name "John Doe" joined project "Shop"
And user with name "John Doe" left project "Shop"
When I visit dashboard page
- Then I should see "John Doe left project at Shop" event
+ Then I should see "John Doe left project Shop" event
diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature
index 41de0ae831..ec5680caba 100644
--- a/features/dashboard/event_filters.feature
+++ b/features/dashboard/event_filters.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Event filters
+Feature: Event Filters
Background:
Given I sign in as a user
And I own a project
diff --git a/features/profile/group.feature b/features/dashboard/group.feature
similarity index 73%
rename from features/profile/group.feature
rename to features/dashboard/group.feature
index e2fbfde77b..cf4b8d7283 100644
--- a/features/profile/group.feature
+++ b/features/dashboard/group.feature
@@ -1,5 +1,5 @@
-@profile
-Feature: Profile Group
+@dashboard
+Feature: Dashboard Group
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
@@ -10,18 +10,18 @@ Feature: Profile Group
@javascript
Scenario: Owner should be able to leave from group if he is not the last owner
Given "Mary Jane" is owner of group "Owned"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Owned"
- And I visit profile groups page
+ And I visit dashboard groups page
Then I should not see group "Owned" in group list
Then I should see group "Guest" in group list
@javascript
Scenario: Owner should not be able to leave from group if he is the last owner
Given "Mary Jane" is guest of group "Owned"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
Then I should not see the "Leave" button for group "Owned"
@@ -29,20 +29,28 @@ Feature: Profile Group
@javascript
Scenario: Guest should be able to leave from group
Given "Mary Jane" is guest of group "Guest"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Guest"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should not see group "Guest" in group list
@javascript
Scenario: Guest should be able to leave from group even if he is the only user in the group
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Guest"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should not see group "Guest" in group list
+
+ Scenario: Create a group from dasboard
+ And I visit dashboard groups page
+ And I click new group link
+ And submit form with new group "Samurai" info
+ Then I should be redirected to group "Samurai" page
+ And I should see newly created group "Samurai"
+
diff --git a/features/dashboard/help.feature b/features/dashboard/help.feature
index 56a0a860ab..bca2772897 100644
--- a/features/dashboard/help.feature
+++ b/features/dashboard/help.feature
@@ -1,5 +1,5 @@
@dashboard
-Feature: Help
+Feature: Dashboard Help
Background:
Given I sign in as a user
And I visit the "Rake Tasks" help page
diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature
index 72627e43e0..99dad88a40 100644
--- a/features/dashboard/issues.feature
+++ b/features/dashboard/issues.feature
@@ -10,10 +10,12 @@ Feature: Dashboard Issues
Scenario: I should see assigned issues
Then I should see issues assigned to me
+ @javascript
Scenario: I should see authored issues
When I click "Authored by me" link
Then I should see issues authored by me
+ @javascript
Scenario: I should see all issues
When I click "All" link
Then I should see all issues
diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature
index dcef1290e7..4a2c997d70 100644
--- a/features/dashboard/merge_requests.feature
+++ b/features/dashboard/merge_requests.feature
@@ -10,10 +10,12 @@ Feature: Dashboard Merge Requests
Scenario: I should see assigned merge_requests
Then I should see merge requests assigned to me
+ @javascript
Scenario: I should see authored merge_requests
When I click "Authored by me" link
Then I should see merge requests authored by me
+ @javascript
Scenario: I should see all merge_requests
When I click "All" link
Then I should see all merge requests
diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature
new file mode 100644
index 0000000000..431dc4ccfc
--- /dev/null
+++ b/features/dashboard/new_project.feature
@@ -0,0 +1,13 @@
+@dashboard
+Feature: New Project
+Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And I visit dashboard page
+
+ @javascript
+ Scenario: I should see New projects page
+ Given I click "New project" link
+ Then I see "New project" page
+ When I click on "Import project from GitHub"
+ Then I see instructions on how to import from GitHub
diff --git a/features/dashboard/projects.feature b/features/dashboard/projects.feature
deleted file mode 100644
index 5214cfc28e..0000000000
--- a/features/dashboard/projects.feature
+++ /dev/null
@@ -1,9 +0,0 @@
-@dashboard
-Feature: Dashboard projects
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And I visit dashboard projects page
-
- Scenario: I should see projects list
- Then I should see projects list
diff --git a/features/dashboard/search.feature b/features/dashboard/search.feature
deleted file mode 100644
index 24c4502869..0000000000
--- a/features/dashboard/search.feature
+++ /dev/null
@@ -1,10 +0,0 @@
-@dashboard
-Feature: Dashboard Search
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And I visit dashboard search page
-
- Scenario: I should see project I am looking for
- Given I search for "Sho"
- Then I should see "Shop" project link
diff --git a/features/dashboard/shortcuts.feature b/features/dashboard/shortcuts.feature
new file mode 100644
index 0000000000..41d79aa6ec
--- /dev/null
+++ b/features/dashboard/shortcuts.feature
@@ -0,0 +1,21 @@
+@dashboard
+Feature: Dashboard Shortcuts
+ Background:
+ Given I sign in as a user
+ And I visit dashboard page
+
+ @javascript
+ Scenario: Navigate to projects tab
+ Given I press "g" and "p"
+ Then the active main tab should be Projects
+
+ @javascript
+ Scenario: Navigate to issue tab
+ Given I press "g" and "i"
+ Then the active main tab should be Issues
+
+ @javascript
+ Scenario: Navigate to merge requests tab
+ Given I press "g" and "m"
+ Then the active main tab should be Merge Requests
+
diff --git a/features/dashboard/starred_projects.feature b/features/dashboard/starred_projects.feature
new file mode 100644
index 0000000000..9dfd2fbab9
--- /dev/null
+++ b/features/dashboard/starred_projects.feature
@@ -0,0 +1,12 @@
+@dashboard
+Feature: Dashboard Starred Projects
+ Background:
+ Given I sign in as a user
+ And public project "Community"
+ And I starred project "Community"
+ And I own project "Shop"
+ And I visit dashboard starred projects page
+
+ Scenario: I should see projects list
+ Then I should see project "Community"
+ And I should not see project "Shop"
diff --git a/features/explore/public_groups.feature b/features/explore/groups.feature
similarity index 97%
rename from features/explore/public_groups.feature
rename to features/explore/groups.feature
index 825e3da934..c11634bd74 100644
--- a/features/explore/public_groups.feature
+++ b/features/explore/groups.feature
@@ -1,5 +1,5 @@
@public
-Feature: Explore Groups Feature
+Feature: Explore Groups
Background:
Given group "TestGroup" has private project "Enterprise"
@@ -28,7 +28,6 @@ Feature: Explore Groups Feature
Given group "TestGroup" has internal project "Internal"
When I sign in as a user
And I visit group "TestGroup" issues page
- And I change filter to Everyone's
Then I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -36,7 +35,6 @@ Feature: Explore Groups Feature
Given group "TestGroup" has internal project "Internal"
When I sign in as a user
And I visit group "TestGroup" merge requests page
- And I change filter to Everyone's
Then I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -94,7 +92,6 @@ Feature: Explore Groups Feature
Given group "TestGroup" has public project "Community"
When I sign in as a user
And I visit group "TestGroup" issues page
- And I change filter to Everyone's
Then I should see project "Community" items
And I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -104,7 +101,6 @@ Feature: Explore Groups Feature
Given group "TestGroup" has public project "Community"
When I sign in as a user
And I visit group "TestGroup" merge requests page
- And I change filter to Everyone's
Then I should see project "Community" items
And I should see project "Internal" items
And I should not see project "Enterprise" items
diff --git a/features/explore/projects.feature b/features/explore/projects.feature
index 9827ebe3e5..a1b2972267 100644
--- a/features/explore/projects.feature
+++ b/features/explore/projects.feature
@@ -1,5 +1,5 @@
@public
-Feature: Explore Projects Feature
+Feature: Explore Projects
Background:
Given public project "Community"
And internal project "Internal"
diff --git a/features/group.feature b/features/groups.feature
similarity index 91%
rename from features/group.feature
rename to features/groups.feature
index b5ff03db84..415e43d6ae 100644
--- a/features/group.feature
+++ b/features/groups.feature
@@ -10,14 +10,6 @@ Feature: Groups
Then I should see group "Owned" projects list
And I should see projects activity feed
- Scenario: Create a group from dasboard
- When I visit group "Owned" page
- And I visit dashboard page
- And I click new group link
- And submit form with new group "Samurai" info
- Then I should be redirected to group "Samurai" page
- And I should see newly created group "Samurai"
-
Scenario: I should see group "Owned" issues list
Given project from group "Owned" has issues assigned to me
When I visit group "Owned" issues page
@@ -55,6 +47,21 @@ Feature: Groups
Then I should not see group "Owned" avatar
And I should not see the "Remove avatar" button
+ @javascript
+ Scenario: Add user to group
+ Given gitlab user "Mike"
+ When I visit group "Owned" members page
+ And I click link "Add members"
+ When I select "Mike" as "Reporter"
+ Then I should see "Mike" in team list as "Reporter"
+
+ @javascript
+ Scenario: Invite user to group
+ When I visit group "Owned" members page
+ And I click link "Add members"
+ When I select "sjobs@apple.com" as "Reporter"
+ Then I should see "sjobs@apple.com" in team list as invited "Reporter"
+
# Leave
@javascript
diff --git a/features/invites.feature b/features/invites.feature
new file mode 100644
index 0000000000..dc8eefaeae
--- /dev/null
+++ b/features/invites.feature
@@ -0,0 +1,45 @@
+Feature: Invites
+ Background:
+ Given "John Doe" is owner of group "Owned"
+ And "John Doe" has invited "user@example.com" to group "Owned"
+
+ Scenario: Viewing invitation when signed out
+ When I visit the invitation page
+ Then I should be redirected to the sign in page
+ And I should see a notice telling me to sign in
+
+ Scenario: Signing in to view invitation
+ When I visit the invitation page
+ And I sign in as "Mary Jane"
+ Then I should be redirected to the invitation page
+
+ Scenario: Viewing invitation when signed in
+ Given I sign in as "Mary Jane"
+ And I visit the invitation page
+ Then I should see the invitation details
+ And I should see an "Accept invitation" button
+ And I should see a "Decline" button
+
+ Scenario: Viewing invitation as an existing member
+ Given I sign in as "John Doe"
+ And I visit the invitation page
+ Then I should see a message telling me I'm already a member
+
+ Scenario: Accepting the invitation
+ Given I sign in as "Mary Jane"
+ And I visit the invitation page
+ And I click the "Accept invitation" button
+ Then I should be redirected to the group page
+ And I should see a notice telling me I have access
+
+ Scenario: Declining the application when signed in
+ Given I sign in as "Mary Jane"
+ And I visit the invitation page
+ And I click the "Decline" button
+ Then I should be redirected to the dashboard
+ And I should see a notice telling me I have declined
+
+ Scenario: Declining the application when signed out
+ When I visit the invitation's decline page
+ Then I should be redirected to the sign in page
+ And I should see a notice telling me I have declined
diff --git a/features/profile/active_tab.feature b/features/profile/active_tab.feature
index a99409d9fd..7801ae5b8c 100644
--- a/features/profile/active_tab.feature
+++ b/features/profile/active_tab.feature
@@ -1,5 +1,5 @@
@profile
-Feature: Profile active tab
+Feature: Profile Active Tab
Background:
Given I sign in as a user
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index d2125e013b..d586167cdf 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -71,6 +71,20 @@ Feature: Profile
And I click on my profile picture
Then I should see my user page
+ Scenario: I can manage application
+ Given I visit profile applications page
+ Then I click on new application button
+ And I should see application form
+ Then I fill application form out and submit
+ And I see application
+ Then I click edit
+ And I see edit application form
+ Then I change name of application and submit
+ And I see that application was changed
+ Then I visit profile applications page
+ And I click to remove application
+ Then I see that application is removed
+
@javascript
Scenario: I change my application theme
Given I visit profile design page
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index ce90a08668..05faad4e64 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -1,4 +1,4 @@
-Feature: Project active tab
+Feature: Project Active Tab
Background:
Given I sign in as a user
And I own a project
@@ -106,24 +106,19 @@ Feature: Project active tab
And no other sub tabs should be active
And the active main tab should be Commits
- # Sub Tabs: Issues
-
Scenario: On Project Issues/Browse
Given I visit my project's issues page
- Then the active sub tab should be Browse Issues
- And no other sub tabs should be active
- And the active main tab should be Issues
+ Then the active main tab should be Issues
+ And no other main tabs should be active
Scenario: On Project Issues/Milestones
Given I visit my project's issues page
And I click the "Milestones" tab
- Then the active sub tab should be Milestones
- And no other sub tabs should be active
- And the active main tab should be Issues
+ Then the active main tab should be Milestones
+ And no other main tabs should be active
Scenario: On Project Issues/Labels
Given I visit my project's issues page
And I click the "Labels" tab
- Then the active sub tab should be Labels
- And no other sub tabs should be active
- And the active main tab should be Issues
+ Then the active main tab should be Labels
+ And no other main tabs should be active
diff --git a/features/project/archived.feature b/features/project/archived.feature
index 9aac29384b..ad466f4f30 100644
--- a/features/project/archived.feature
+++ b/features/project/archived.feature
@@ -14,15 +14,6 @@ Feature: Project Archived
And I visit project "Forum" page
Then I should see "Archived"
- Scenario: I should not see archived on projects page with no archived projects
- And I visit dashboard projects page
- Then I should not see "Archived"
-
- Scenario: I should see archived on projects page with archived projects
- And project "Forum" is archived
- And I visit dashboard projects page
- Then I should see "Archived"
-
Scenario: I archive project
When project "Shop" has push event
And I visit project "Shop" page
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
index abebef04fc..65d8e48b9b 100644
--- a/features/project/commits/branches.feature
+++ b/features/project/commits/branches.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse branches
+Feature: Project Commits Branches
Background:
Given I sign in as a user
And I own project "Shop"
@@ -15,5 +15,29 @@ Feature: Project Browse branches
Scenario: I create a branch
Given I visit project branches page
And I click new branch link
- When I submit new branch form
+ And I submit new branch form
Then I should see new branch created
+
+ @javascript
+ Scenario: I delete a branch
+ Given I visit project branches page
+ And I click branch 'improve/awesome' delete link
+ Then I should not see branch 'improve/awesome'
+
+ Scenario: I create a branch with invalid name
+ Given I visit project branches page
+ And I click new branch link
+ And I submit new branch form with invalid name
+ Then I should see new an error that branch is invalid
+
+ Scenario: I create a branch with invalid reference
+ Given I visit project branches page
+ And I click new branch link
+ And I submit new branch form with invalid reference
+ Then I should see new an error that ref is invalid
+
+ Scenario: I create a branch that already exists
+ Given I visit project branches page
+ And I click new branch link
+ And I submit new branch form with branch that already exists
+ Then I should see new an error that branch already exists
diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature
index a1aa745a68..c41075d7ad 100644
--- a/features/project/commits/comments.feature
+++ b/features/project/commits/comments.feature
@@ -1,4 +1,4 @@
-Feature: Comments on commits
+Feature: Project Commits Comments
Background:
Given I sign in as a user
And I own project "Shop"
@@ -13,15 +13,10 @@ Feature: Comments on commits
Scenario: I can't cancel the main form
Then I should not see the cancel comment button
- @javascript
- Scenario: I can't preview without text
- Given I haven't written any comment text
- Then I should not see the comment preview button
-
@javascript
Scenario: I can preview with text
- Given I write a comment like "Nice"
- Then I should see the comment preview button
+ Given I write a comment like ":+1: Nice"
+ Then The comment preview tab should be display rendered Markdown
@javascript
Scenario: I preview a comment
@@ -32,7 +27,7 @@ Feature: Comments on commits
@javascript
Scenario: I can edit after preview
Given I preview a comment text like "Bug fixed :smile:"
- Then I should see the comment edit button
+ Then I should see the comment write tab
@javascript
Scenario: I have a reset form after posting from preview
@@ -46,3 +41,9 @@ Feature: Comments on commits
Given I leave a comment like "XML attached"
And I delete a comment
Then I should not see a comment saying "XML attached"
+
+ @javascript
+ Scenario: I can edit a comment with +1
+ Given I leave a comment like "XML attached"
+ And I edit the last comment with a +1
+ Then I should see +1 in the description
diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature
index 7c6db3c465..c4b206edc9 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse commits
+Feature: Project Commits
Background:
Given I sign in as a user
And I own a project
@@ -21,18 +21,22 @@ Feature: Project Browse commits
And I click side-by-side diff button
Then I see inline diff button
+ @javascript
Scenario: I compare refs
Given I visit compare refs page
And I fill compare fields with refs
Then I see compared refs
+ And I unfold diff
+ Then I should see additional file lines
Scenario: I browse commits for a specific path
Given I visit my project's commits page for a specific path
Then I see breadcrumb links
- Scenario: I browse commits stats
- Given I visit my project's commits stats page
- Then I see commits stats
+ # TODO: Implement feature in graphs
+ #Scenario: I browse commits stats
+ #Given I visit my project's commits stats page
+ #Then I see commits stats
Scenario: I browse big commit
Given I visit big commit page
diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index b26019f832..56b9a13678 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -1,4 +1,4 @@
-Feature: Comments on commit diffs
+Feature: Project Commits Diff Comments
Background:
Given I sign in as a user
And I own project "Shop"
@@ -54,17 +54,11 @@ Feature: Comments on commit diffs
Given I leave a diff comment like "Typo, please fix"
Then I should see a discussion reply button
- @javascript
- Scenario: I can't preview without text
- Given I open a diff comment form
- And I haven't written any diff comment text
- Then I should not see the diff comment preview button
-
@javascript
Scenario: I can preview with text
Given I open a diff comment form
And I write a diff comment like ":-1: I don't like this"
- Then I should see the diff comment preview button
+ Then The diff comment preview tab should display rendered Markdown
@javascript
Scenario: I preview a diff comment
@@ -75,7 +69,7 @@ Feature: Comments on commit diffs
@javascript
Scenario: I can edit after preview
Given I preview a diff comment text like "Should fix it :smile:"
- Then I should see the diff comment edit button
+ Then I should see the diff comment write tab
@javascript
Scenario: The form gets removed after posting
diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature
index 1ac0f8bfa4..02f399f7ca 100644
--- a/features/project/commits/tags.feature
+++ b/features/project/commits/tags.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse tags
+Feature: Project Commits Tags
Background:
Given I sign in as a user
And I own project "Shop"
@@ -7,5 +7,35 @@ Feature: Project Browse tags
Scenario: I can see all git tags
Then I should see "Shop" all tags list
+ Scenario: I create a tag
+ And I click new tag link
+ And I submit new tag form
+ Then I should see new tag created
+
+ Scenario: I create a tag with invalid name
+ And I click new tag link
+ And I submit new tag form with invalid name
+ Then I should see new an error that tag is invalid
+
+ Scenario: I create a tag with invalid reference
+ And I click new tag link
+ And I submit new tag form with invalid reference
+ Then I should see new an error that tag ref is invalid
+
+ Scenario: I create a tag that already exists
+ And I click new tag link
+ And I submit new tag form with tag that already exists
+ Then I should see new an error that tag already exists
+
+ @javascript
+ Scenario: I delete a tag
+ Given I delete tag 'v1.1.0'
+ Then I should not see tag 'v1.1.0'
+
+ @javascript
+ Scenario: I delete all tags and see info message
+ Given I delete all tags
+ Then I should see tags info message
+
# @wip
# Scenario: I can download project by tag
diff --git a/features/project/commits/user_lookup.feature b/features/project/commits/user_lookup.feature
index 7b194ab920..db51d4a6cf 100644
--- a/features/project/commits/user_lookup.feature
+++ b/features/project/commits/user_lookup.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse Commits User Lookup
+Feature: Project Commits User Lookup
Background:
Given I sign in as a user
And I own a project
diff --git a/features/project/create.feature b/features/project/create.feature
index bb8e3a368e..e9dc4fe6b3 100644
--- a/features/project/create.feature
+++ b/features/project/create.feature
@@ -1,4 +1,4 @@
-Feature: Create Project
+Feature: Project Create
In order to get access to project sections
A user with ability to create a project
Should be able to create a new one
diff --git a/features/project/deploy_keys.feature b/features/project/deploy_keys.feature
index 13e3b9bbd2..a71f6124d9 100644
--- a/features/project/deploy_keys.feature
+++ b/features/project/deploy_keys.feature
@@ -6,7 +6,17 @@ Feature: Project Deploy Keys
Scenario: I should see deploy keys list
Given project has deploy key
When I visit project deploy keys page
- Then I should see project deploy keys
+ Then I should see project deploy key
+
+ Scenario: I should see project deploy keys
+ Given other project has deploy key
+ When I visit project deploy keys page
+ Then I should see other project deploy key
+
+ Scenario: I should see public deploy keys
+ Given public deploy key exists
+ When I visit project deploy keys page
+ Then I should see public deploy key
Scenario: I add new deploy key
Given I visit project deploy keys page
@@ -15,9 +25,16 @@ Feature: Project Deploy Keys
Then I should be on deploy keys page
And I should see newly created deploy key
- Scenario: I attach deploy key to project
+ Scenario: I attach other project deploy key to project
Given other project has deploy key
And I visit project deploy keys page
When I click attach deploy key
Then I should be on deploy keys page
And I should see newly created deploy key
+
+ Scenario: I attach public deploy key to project
+ Given public deploy key exists
+ And I visit project deploy keys page
+ When I click attach deploy key
+ Then I should be on deploy keys page
+ And I should see newly created deploy key
diff --git a/features/project/edit_issuetracker.feature b/features/project/edit_issuetracker.feature
deleted file mode 100644
index cc0de07ca6..0000000000
--- a/features/project/edit_issuetracker.feature
+++ /dev/null
@@ -1,18 +0,0 @@
-Feature: Project Issue Tracker
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And project "Shop" has issues enabled
- And I visit project "Shop" page
-
- Scenario: I set the issue tracker to "GitLab"
- When I visit edit project "Shop" page
- And change the issue tracker to "GitLab"
- And I save project
- Then I the project should have "GitLab" as issue tracker
-
- Scenario: I set the issue tracker to "Redmine"
- When I visit edit project "Shop" page
- And change the issue tracker to "Redmine"
- And I save project
- Then I the project should have "Redmine" as issue tracker
diff --git a/features/project/fork.feature b/features/project/fork.feature
index dc477ca3bf..22f68e5b34 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -1,4 +1,4 @@
-Feature: Fork Project
+Feature: Project Fork
Background:
Given I sign in as a user
And I am a member of project "Shop"
@@ -6,9 +6,11 @@ Feature: Fork Project
Scenario: User fork a project
Given I click link "Fork"
+ When I fork to my namespace
Then I should see the forked project page
Scenario: User already has forked the project
Given I already have a project named "Shop" in my namespace
And I click link "Fork"
+ When I fork to my namespace
Then I should see a "Name has already been taken" warning
diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature
index 7442145d87..d9fbb875c2 100644
--- a/features/project/forked_merge_requests.feature
+++ b/features/project/forked_merge_requests.feature
@@ -11,18 +11,20 @@ Feature: Project Forked Merge Requests
And I submit the merge request
Then I should see merge request "Merge Request On Forked Project"
- @javascript
- Scenario: I can edit a forked merge request
- Given I visit project "Forked Shop" merge requests page
- And I click link "New Merge Request"
- And I fill out a "Merge Request On Forked Project" merge request
- And I submit the merge request
- And I should see merge request "Merge Request On Forked Project"
- And I click link edit "Merge Request On Forked Project"
- Then I see the edit page prefilled for "Merge Request On Forked Project"
- And I update the merge request title
- And I save the merge request
- Then I should see the edited merge request
+ # TODO: Improve it so it does not fail randomly
+ #
+ #@javascript
+ #Scenario: I can edit a forked merge request
+ #Given I visit project "Forked Shop" merge requests page
+ #And I click link "New Merge Request"
+ #And I fill out a "Merge Request On Forked Project" merge request
+ #And I submit the merge request
+ #And I should see merge request "Merge Request On Forked Project"
+ #And I click link edit "Merge Request On Forked Project"
+ #Then I see the edit page prefilled for "Merge Request On Forked Project"
+ #And I update the merge request title
+ #And I save the merge request
+ #Then I should see the edited merge request
@javascript
Scenario: I cannot submit an invalid merge request
diff --git a/features/project/graph.feature b/features/project/graph.feature
index cda95f5dda..89064242c1 100644
--- a/features/project/graph.feature
+++ b/features/project/graph.feature
@@ -2,8 +2,13 @@ Feature: Project Graph
Background:
Given I sign in as a user
And I own project "Shop"
- And I visit project "Shop" graph page
@javascript
Scenario: I should see project graphs
+ When I visit project "Shop" graph page
Then page should have graphs
+
+ @javascript
+ Scenario: I should see project commits graphs
+ When I visit project "Shop" commits graph page
+ Then page should have commits graphs
diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature
index f4a0a7977c..e316f51986 100644
--- a/features/project/issues/filter_labels.feature
+++ b/features/project/issues/filter_labels.feature
@@ -1,4 +1,4 @@
-Feature: Project Filter Labels
+Feature: Project Issues Filter Labels
Background:
Given I sign in as a user
And I own project "Shop"
@@ -8,11 +8,7 @@ Feature: Project Filter Labels
And project "Shop" has issue "Feature1" with labels: "feature"
Given I visit project "Shop" issues page
- Scenario: I should see project issues
- Then I should see "bug" in labels filter
- And I should see "feature" in labels filter
- And I should see "enhancement" in labels filter
-
+ @javascript
Scenario: I filter by one label
Given I click link "bug"
Then I should see "Bugfix1" in issues list
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index b2e6f1f932..eb813884d1 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -25,6 +25,12 @@ Feature: Project Issues
Given I click link "Release 0.4"
Then I should see issue "Release 0.4"
+ @javascript
+ Scenario: I visit issue page
+ Given I add a user to project "Shop"
+ And I click "author" dropdown
+ Then I see current user as the first user
+
Scenario: I submit new unassigned issue
Given I click link "New Issue"
And I submit new issue "500 error on profile"
@@ -42,6 +48,7 @@ Feature: Project Issues
Given I visit issue page "Release 0.4"
And I leave a comment like "XML attached"
Then I should see comment "XML attached"
+ And I should see an error alert section within the comment form
@javascript
Scenario: I search issue
@@ -63,6 +70,36 @@ Feature: Project Issues
Then I should see "Release 0.3" in issues
And I should not see "Release 0.4" in issues
+ @javascript
+ Scenario: Search issues when search string exactly matches issue description
+ Given project 'Shop' has issue 'Bugfix1' with description: 'Description for issue1'
+ And I fill in issue search with 'Description for issue1'
+ Then I should see 'Bugfix1' in issues
+ And I should not see "Release 0.4" in issues
+ And I should not see "Release 0.3" in issues
+ And I should not see "Tweet control" in issues
+
+ @javascript
+ Scenario: Search issues when search string partially matches issue description
+ Given project 'Shop' has issue 'Bugfix1' with description: 'Description for issue1'
+ And project 'Shop' has issue 'Feature1' with description: 'Feature submitted for issue1'
+ And I fill in issue search with 'issue1'
+ Then I should see 'Feature1' in issues
+ Then I should see 'Bugfix1' in issues
+ And I should not see "Release 0.4" in issues
+ And I should not see "Release 0.3" in issues
+ And I should not see "Tweet control" in issues
+
+ @javascript
+ Scenario: Search issues when search string matches no issue description
+ Given project 'Shop' has issue 'Bugfix1' with description: 'Description for issue1'
+ And I fill in issue search with 'Rock and roll'
+ Then I should not see 'Bugfix1' in issues
+ And I should not see "Release 0.4" in issues
+ And I should not see "Release 0.3" in issues
+ And I should not see "Tweet control" in issues
+
+
# Markdown
Scenario: Headers inside the description should have ids generated for them.
@@ -89,3 +126,94 @@ Feature: Project Issues
Given I click link "New Issue"
And I submit new issue "500 error on profile"
Then I should see issue "500 error on profile"
+
+ Scenario: Clickable labels
+ Given issue 'Release 0.4' has label 'bug'
+ And I visit project "Shop" issues page
+ When I click label 'bug'
+ And I should see "Release 0.4" in issues
+ And I should not see "Tweet control" in issues
+
+ Scenario: Issue description should render task checkboxes
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ Then I should see task checkboxes in the description
+
+ @javascript
+ Scenario: Issue notes should not render task checkboxes
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ And I leave a comment with task markdown
+ Then I should not see task checkboxes in the comment
+
+ @javascript
+ Scenario: Issue notes should be editable with +1
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ And I leave a comment with a header containing "Comment with a header"
+ Then The comment with the header should not have an ID
+ And I edit the last comment with a +1
+ Then I should see +1 in the description
+
+ # Task status in issues list
+
+ Scenario: Issues list should display task status
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit project "Shop" issues page
+ Then I should see the task status for the Taskable
+
+ # Toggling task items
+
+ @javascript
+ Scenario: Task checkboxes should be enabled for an open issue
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ Then Task checkboxes should be enabled
+
+ @javascript
+ Scenario: Task checkboxes should be disabled for a closed issue
+ Given project "Shop" has "Tasks-closed" closed issue with task markdown
+ When I visit issue page "Tasks-closed"
+ Then Task checkboxes should be disabled
+
+ # Issue description preview
+
+ @javascript
+ Scenario: I can't preview without text
+ Given I click link "New Issue"
+ And I haven't written any description text
+ Then The Markdown preview tab should say there is nothing to do
+
+ @javascript
+ Scenario: I can preview with text
+ Given I click link "New Issue"
+ And I write a description like ":+1: Nice"
+ Then The Markdown preview tab should display rendered Markdown
+
+ @javascript
+ Scenario: I preview an issue description
+ Given I click link "New Issue"
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown preview
+ And I should not see the Markdown text field
+
+ @javascript
+ Scenario: I can edit after preview
+ Given I click link "New Issue"
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
+
+ @javascript
+ Scenario: I can preview when editing an existing issue
+ Given I click link "Release 0.4"
+ And I click link "Edit" for the issue
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
+
+ @javascript
+ Scenario: I can unsubscribe from issue
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ Then I should see that I am subscribed
+ When I click button "Unsubscribe"
+ Then I should see that I am unsubscribed
diff --git a/features/project/issues/labels.feature b/features/project/issues/labels.feature
index 29cf530727..039a7d83cb 100644
--- a/features/project/issues/labels.feature
+++ b/features/project/issues/labels.feature
@@ -1,4 +1,4 @@
-Feature: Project Labels
+Feature: Project Issues Labels
Background:
Given I sign in as a user
And I own project "Shop"
@@ -6,8 +6,8 @@ Feature: Project Labels
Given I visit project "Shop" labels page
Scenario: I should see labels list
- Then I should see label "bug"
- And I should see label "feature"
+ Then I should see label 'bug'
+ And I should see label 'feature'
Scenario: I create new label
Given I visit project "Shop" new label page
@@ -24,6 +24,11 @@ Feature: Project Labels
When I remove label 'bug'
Then I should not see label 'bug'
+ @javascript
+ Scenario: I remove all labels
+ When I delete all labels
+ Then I should see labels help message
+
Scenario: I create a label with invalid color
Given I visit project "Shop" new label page
When I submit new label with invalid color
@@ -40,4 +45,3 @@ Feature: Project Labels
And I visit project "Forum" new label page
When I submit new label 'bug'
Then I should see label 'bug'
-
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
index e67b5d2d86..9ac65b1257 100644
--- a/features/project/issues/milestones.feature
+++ b/features/project/issues/milestones.feature
@@ -1,4 +1,4 @@
-Feature: Project Milestones
+Feature: Project Issues Milestones
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 8b6c296dfe..cbb5c8eb39 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -96,6 +96,16 @@ Feature: Project Merge Requests
And I leave a comment with a header containing "Comment with a header"
Then The comment with the header should not have an ID
+ Scenario: Merge request description should render task checkboxes
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ When I visit merge request page "MR-task-open"
+ Then I should see task checkboxes in the description
+
+ Scenario: Merge request notes should not render task checkboxes
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ When I visit merge request page "MR-task-open"
+ Then I should not see task checkboxes in the comment
+
# Toggling inline comments
@javascript
@@ -105,7 +115,7 @@ Feature: Project Merge Requests
And I switch to the diff tab
And I leave a comment like "Line is wrong" on line 39 of the second file
And I click link "Hide inline discussion" of the second file
- Then I should not see a comment like "Line is wrong" in the second file
+ Then I should not see a comment like "Line is wrong here" in the second file
@javascript
Scenario: I show comments on a merge request diff with comments in a single file
@@ -113,8 +123,6 @@ Feature: Project Merge Requests
And I visit merge request page "Bug NS-05"
And I switch to the diff tab
And I leave a comment like "Line is wrong" on line 39 of the second file
- And I click link "Hide inline discussion" of the second file
- And I click link "Show inline discussion" of the second file
Then I should see a comment like "Line is wrong" in the second file
@javascript
@@ -125,7 +133,7 @@ Feature: Project Merge Requests
And I leave a comment like "Line is correct" on line 12 of the first file
And I leave a comment like "Line is wrong" on line 39 of the second file
And I click link "Hide inline discussion" of the second file
- Then I should not see a comment like "Line is wrong" in the second file
+ Then I should not see a comment like "Line is wrong here" in the second file
And I should still see a comment like "Line is correct" in the first file
@javascript
@@ -147,3 +155,87 @@ Feature: Project Merge Requests
And I switch to the diff tab
And I unfold diff
Then I should see additional file lines
+
+ @javascript
+ Scenario: I show comments on a merge request side-by-side diff with comments in multiple files
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is correct" on line 12 of the first file
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click Side-by-side Diff tab
+ Then I should see comments on the side-by-side diff page
+
+ @javascript
+ Scenario: I view diffs on a merge request
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I click on the Changes tab via Javascript
+ Then I should see the proper Inline and Side-by-side links
+
+ # Task status in issues list
+
+ Scenario: Merge requests list should display task status
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ When I visit project "Shop" merge requests page
+ Then I should see the task status for the Taskable
+
+ # Toggling task items
+
+ @javascript
+ Scenario: Task checkboxes should be enabled for an open merge request
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ When I visit merge request page "MR-task-open"
+ Then Task checkboxes should be enabled
+
+ @javascript
+ Scenario: Task checkboxes should be disabled for a closed merge request
+ Given project "Shop" has "MR-task-open" open MR with task markdown
+ And I visit merge request page "MR-task-open"
+ And I click link "Close"
+ Then Task checkboxes should be disabled
+
+ # Description preview
+
+ @javascript
+ Scenario: I can't preview without text
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I haven't written any description text
+ Then The Markdown preview tab should say there is nothing to do
+
+ @javascript
+ Scenario: I can preview with text
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I write a description like ":+1: Nice"
+ Then The Markdown preview tab should display rendered Markdown
+
+ @javascript
+ Scenario: I preview a merge request description
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown preview
+ And I should not see the Markdown text field
+
+ @javascript
+ Scenario: I can edit after preview
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
+
+ @javascript
+ Scenario: I search merge request
+ Given I click link "All"
+ When I fill in merge request search with "Fe"
+ Then I should see "Feature NS-03" in merge requests
+ And I should not see "Bug NS-04" in merge requests
+
+ @javascript
+ Scenario: I can unsubscribe from merge request
+ Given I visit merge request page "Bug NS-04"
+ Then I should see that I am subscribed
+ When I click button "Unsubscribe"
+ Then I should see that I am unsubscribed
diff --git a/features/project/network.feature b/features/project/network_graph.feature
similarity index 100%
rename from features/project/network.feature
rename to features/project/network_graph.feature
diff --git a/features/project/project.feature b/features/project/project.feature
index c1f192f123..3e1fd54bee 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -1,10 +1,23 @@
-Feature: Project Feature
+Feature: Project
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" has push event
And I visit project "Shop" page
+ Scenario: I edit the project avatar
+ Given I visit edit project "Shop" page
+ When I change the project avatar
+ And I should see new project avatar
+ And I should see the "Remove avatar" button
+
+ Scenario: I remove the project avatar
+ Given I visit edit project "Shop" page
+ And I have an project avatar
+ When I remove my project avatar
+ Then I should see the default project avatar
+ And I should not see the "Remove avatar" button
+
@javascript
Scenario: I should see project activity
When I visit project "Shop" page
@@ -27,7 +40,6 @@ Feature: Project Feature
Scenario: I should see project readme and version
When I visit project "Shop" page
- Then I should see project "Shop" README link
And I should see project "Shop" version
Scenario: I should change project default branch
@@ -35,3 +47,11 @@ Feature: Project Feature
And change project default branch
And I save project
Then I should see project default branch changed
+
+ @javascript
+ Scenario: I should have default tab per my preference
+ And I own project "Forum"
+ When I select project "Forum" README tab
+ Then I should see project "Forum" README
+ And I visit project "Shop" page
+ Then I should see project "Shop" README
diff --git a/features/project/service.feature b/features/project/service.feature
index a5af065c9e..fdff640ec8 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -19,6 +19,12 @@ Feature: Project Services
And I fill hipchat settings
Then I should see hipchat service settings saved
+ Scenario: Activate hipchat service with custom server
+ When I visit project "Shop" services page
+ And I click hipchat service link
+ And I fill hipchat settings with custom server
+ Then I should see hipchat service settings with custom server saved
+
Scenario: Activate pivotaltracker service
When I visit project "Shop" services page
And I click pivotaltracker service link
@@ -43,8 +49,38 @@ Feature: Project Services
And I fill Slack settings
Then I should see Slack service settings saved
+ Scenario: Activate Pushover service
+ When I visit project "Shop" services page
+ And I click Pushover service link
+ And I fill Pushover settings
+ Then I should see Pushover service settings saved
+
Scenario: Activate email on push service
When I visit project "Shop" services page
And I click email on push service link
And I fill email on push settings
Then I should see email on push service settings saved
+
+ Scenario: Activate Irker (IRC Gateway) service
+ When I visit project "Shop" services page
+ And I click Irker service link
+ And I fill Irker settings
+ Then I should see Irker service settings saved
+
+ Scenario: Activate Atlassian Bamboo CI service
+ When I visit project "Shop" services page
+ And I click Atlassian Bamboo CI service link
+ And I fill Atlassian Bamboo CI settings
+ Then I should see Atlassian Bamboo CI service settings saved
+
+ Scenario: Activate jetBrains TeamCity CI service
+ When I visit project "Shop" services page
+ And I click jetBrains TeamCity CI service link
+ And I fill jetBrains TeamCity CI settings
+ Then I should see jetBrains TeamCity CI service settings saved
+
+ Scenario: Activate Asana service
+ When I visit project "Shop" services page
+ And I click Asana service link
+ And I fill Asana settings
+ Then I should see Asana service settings saved
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
new file mode 100644
index 0000000000..cfb68bf1f5
--- /dev/null
+++ b/features/project/shortcuts.feature
@@ -0,0 +1,52 @@
+@dashboard
+Feature: Project Shortcuts
+ Background:
+ Given I sign in as a user
+ And I own a project
+ And I visit my project's home page
+
+ @javascript
+ Scenario: Navigate to files tab
+ Given I press "g" and "f"
+ Then the active main tab should be Files
+
+ @javascript
+ Scenario: Navigate to commits tab
+ Given I press "g" and "c"
+ Then the active main tab should be Commits
+
+ @javascript
+ Scenario: Navigate to network tab
+ Given I press "g" and "n"
+ Then the active main tab should be Network
+
+ @javascript
+ Scenario: Navigate to graphs tab
+ Given I press "g" and "g"
+ Then the active main tab should be Graphs
+
+ @javascript
+ Scenario: Navigate to issues tab
+ Given I press "g" and "i"
+ Then the active main tab should be Issues
+
+ @javascript
+ Scenario: Navigate to merge requests tab
+ Given I press "g" and "m"
+ Then the active main tab should be Merge Requests
+
+ @javascript
+ Scenario: Navigate to snippets tab
+ Given I press "g" and "s"
+ Then the active main tab should be Snippets
+
+ @javascript
+ Scenario: Navigate to wiki tab
+ Given I press "g" and "w"
+ Then the active main tab should be Wiki
+
+ @javascript
+ Scenario: Navigate to project feed
+ Given I visit my project's files page
+ Given I press "g" and "p"
+ Then the active main tab should be Home
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index f8934da8de..90b966dd64 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse files
+Feature: Project Source Browse Files
Background:
Given I sign in as a user
And I own project "Shop"
@@ -13,41 +13,148 @@ Feature: Project Browse files
Scenario: I browse file content
Given I click on ".gitignore" file in repo
- Then I should see it content
+ Then I should see its content
Scenario: I browse raw file
Given I visit blob file from repo
- And I click link "raw"
+ And I click link "Raw"
Then I should see raw file content
Scenario: I can create file
Given I click on "new file" link in repo
Then I can see new file page
+ @javascript
+ Scenario: I can create and commit file
+ Given I click on "new file" link in repo
+ And I edit code
+ And I fill the new file name
+ And I fill the commit message
+ And I click on "Commit Changes"
+ Then I am redirected to the new file
+ And I should see its new content
+
+ @javascript
+ Scenario: I can create and commit file and specify new branch
+ Given I click on "new file" link in repo
+ And I edit code
+ And I fill the new file name
+ And I fill the commit message
+ And I fill the new branch name
+ And I click on "Commit Changes"
+ Then I am redirected to the new file on new branch
+ And I should see its new content
+
+ @javascript @tricky
+ Scenario: I can create file in empty repo
+ Given I own an empty project
+ And I visit my empty project page
+ And I create bare repo
+ When I click on "add a file" link
+ And I edit code
+ And I fill the new file name
+ And I fill the commit message
+ And I click on "Commit Changes"
+ Then I am redirected to the new file
+ And I should see its new content
+
+ @javascript
+ Scenario: If I enter an illegal file name I see an error message
+ Given I click on "new file" link in repo
+ And I fill the new file name with an illegal name
+ And I edit code
+ And I fill the commit message
+ And I click on "Commit changes"
+ Then I am on the new file page
+ And I see a commit error message
+
@javascript
Scenario: I can edit file
Given I click on ".gitignore" file in repo
- And I click button "edit"
+ And I click button "Edit"
Then I can edit code
+ Scenario: If the file is binary the edit link is hidden
+ Given I visit a binary file in the repo
+ Then I cannot see the edit button
+
+ Scenario: If I don't have edit permission the edit link is disabled
+ Given public project "Community"
+ And I visit project "Community" source page
+ And I click on ".gitignore" file in repo
+ Then The edit button is disabled
+
+ @javascript
+ Scenario: I can edit and commit file
+ Given I click on ".gitignore" file in repo
+ And I click button "Edit"
+ And I edit code
+ And I fill the commit message
+ And I click on "Commit Changes"
+ Then I am redirected to the ".gitignore"
+ And I should see its new content
+
+ @javascript
+ Scenario: I can edit and commit file to new branch
+ Given I click on ".gitignore" file in repo
+ And I click button "Edit"
+ And I edit code
+ And I fill the commit message
+ And I fill the new branch name
+ And I click on "Commit Changes"
+ Then I am redirected to the ".gitignore" on new branch
+ And I should see its new content
+
+ @javascript @wip
+ Scenario: If I don't change the content of the file I see an error message
+ Given I click on ".gitignore" file in repo
+ And I click button "edit"
+ And I fill the commit message
+ And I click on "Commit changes"
+ # Test fails because carriage returns are added to the file.
+ Then I am on the ".gitignore" edit file page
+ And I see a commit error message
+
@javascript
Scenario: I can see editing preview
Given I click on ".gitignore" file in repo
- And I click button "edit"
+ And I click button "Edit"
And I edit code
And I click link "Diff"
Then I see diff
+ @javascript
+ Scenario: I can remove file and commit
+ Given I click on ".gitignore" file in repo
+ And I see the ".gitignore"
+ And I click on "Remove"
+ And I fill the commit message
+ And I click on "Remove file"
+ Then I am redirected to the files URL
+ And I don't see the ".gitignore"
+
Scenario: I can browse directory with Browse Dir
Given I click on files directory
- And I click on history link
+ And I click on History link
Then I see Browse dir link
Scenario: I can browse file with Browse File
Given I click on readme file
- And I click on history link
+ And I click on History link
Then I see Browse file link
Scenario: I can browse code with Browse Code
- Given I click on history link
+ Given I click on History link
Then I see Browse code link
+
+ # Permalink
+
+ Scenario: I click on the permalink link from a branch ref
+ Given I click on ".gitignore" file in repo
+ And I click on Permalink
+ Then I am redirected to the permalink URL
+
+ Scenario: I don't see the permalink link from a SHA ref
+ Given I visit project source page for "6d394385cf567f80a8fd85055db1ab4c5295806f"
+ And I click on ".gitignore" file in repo
+ Then I don't see the permalink link
diff --git a/features/project/source/git_blame.feature b/features/project/source/git_blame.feature
index ae62c166c1..48b1077dc6 100644
--- a/features/project/source/git_blame.feature
+++ b/features/project/source/git_blame.feature
@@ -1,4 +1,4 @@
-Feature: Project Browse git repo
+Feature: Project Source Git Blame
Background:
Given I sign in as a user
And I own project "Shop"
@@ -6,5 +6,5 @@ Feature: Project Browse git repo
Scenario: I blame file
Given I click on ".gitignore" file in repo
- And I click blame button
+ And I click Blame button
Then I should see git file blame
diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature
index fce351317c..ecbd721c28 100644
--- a/features/project/source/markdown_render.feature
+++ b/features/project/source/markdown_render.feature
@@ -1,4 +1,4 @@
-Feature: Project markdown render
+Feature: Project Source Markdown Render
Background:
Given I sign in as a user
And I own project "Delta"
diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature
index f60b646a8d..63b7cb77a9 100644
--- a/features/project/source/multiselect_blob.feature
+++ b/features/project/source/multiselect_blob.feature
@@ -1,4 +1,4 @@
-Feature: Project Multiselect Blob
+Feature: Project Source Multiselect Blob
Background:
Given I sign in as a user
And I own project "Shop"
diff --git a/features/project/source/search_code.feature b/features/project/source/search_code.feature
index 93b326696d..4f9dcea249 100644
--- a/features/project/source/search_code.feature
+++ b/features/project/source/search_code.feature
@@ -1,4 +1,4 @@
-Feature: Project Search code
+Feature: Project Source Search Code
Background:
Given I sign in as a user
diff --git a/features/project/star.feature b/features/project/star.feature
index 3322f89180..a45f9c470e 100644
--- a/features/project/star.feature
+++ b/features/project/star.feature
@@ -13,7 +13,7 @@ Feature: Project Star
Given public project "Community"
And I visit project "Community" page
When I click on the star toggle button
- Then The project has 0 stars
+ Then I redirected to sign in page
@javascript
Scenario: Signed in users can toggle star
diff --git a/features/project/team_management.feature b/features/project/team_management.feature
index e153978e04..6cda225ea7 100644
--- a/features/project/team_management.feature
+++ b/features/project/team_management.feature
@@ -1,4 +1,4 @@
-Feature: Project Team management
+Feature: Project Team Management
Background:
Given I sign in as a user
And I own project "Shop"
@@ -13,10 +13,16 @@ Feature: Project Team management
@javascript
Scenario: Add user to project
- Given I click link "New Team Member"
+ Given I click link "Add members"
And I select "Mike" as "Reporter"
Then I should see "Mike" in team list as "Reporter"
+ @javascript
+ Scenario: Invite user to project
+ Given I click link "Add members"
+ And I select "sjobs@apple.com" as "Reporter"
+ Then I should see "sjobs@apple.com" in team list as invited "Reporter"
+
@javascript
Scenario: Update user access
Given I should see "Sam" in team list as "Developer"
diff --git a/features/project/wiki.feature b/features/project/wiki.feature
index 4a8c771dda..977cd609a1 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -62,3 +62,27 @@ Feature: Project Wiki
And I browse to wiki page with images
And I click on image link
Then I should see the new wiki page form
+
+ @javascript
+ Scenario: New Wiki page that has a path
+ Given I create a New page with paths
+ And I click on the "Pages" button
+ Then I should see non-escaped link in the pages list
+
+ @javascript
+ Scenario: Edit Wiki page that has a path
+ Given I create a New page with paths
+ And I click on the "Pages" button
+ And I edit the Wiki page with a path
+ Then I should see a non-escaped path
+ And I should see the Editing page
+ And I change the content
+ Then I should see the updated content
+
+ @javascript
+ Scenario: View the page history of a Wiki page that has a path
+ Given I create a New page with paths
+ And I click on the "Pages" button
+ And I view the page history of a Wiki page that has a path
+ Then I should see a non-escaped path
+ And I should see the page history
diff --git a/features/search.feature b/features/search.feature
new file mode 100644
index 0000000000..def21e0092
--- /dev/null
+++ b/features/search.feature
@@ -0,0 +1,46 @@
+@dashboard
+Feature: Search
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And I visit dashboard search page
+
+ Scenario: I should see project I am looking for
+ Given I search for "Sho"
+ Then I should see "Shop" project link
+
+ Scenario: I should see issues I am looking for
+ And project has issues
+ When I search for "Foo"
+ And I click "Issues" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
+
+ Scenario: I should see merge requests I am looking for
+ And project has merge requests
+ When I search for "Foo"
+ When I click "Merge requests" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
+
+ Scenario: I should see project code I am looking for
+ When I click project "Shop" link
+ And I search for "rspec"
+ Then I should see code results for project "Shop"
+
+ Scenario: I should see project issues
+ And project has issues
+ When I click project "Shop" link
+ And I search for "Foo"
+ And I click "Issues" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
+
+ Scenario: I should see project merge requests
+ And project has merge requests
+ When I click project "Shop" link
+ And I search for "Foo"
+ And I click "Merge requests" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
+
diff --git a/features/snippet_search.feature b/features/snippet_search.feature
new file mode 100644
index 0000000000..834bd3b237
--- /dev/null
+++ b/features/snippet_search.feature
@@ -0,0 +1,20 @@
+@dashboard
+Feature: Snippet Search
+ Background:
+ Given I sign in as a user
+ And I have public "Personal snippet one" snippet
+ And I have private "Personal snippet private" snippet
+ And I have a public many lined snippet
+
+ Scenario: I should see my public and private snippets
+ When I search for "snippet" in snippet titles
+ Then I should see "Personal snippet one" in results
+ And I should see "Personal snippet private" in results
+
+ Scenario: I should see three surrounding lines on either side of a matching snippet line
+ When I search for "line seven" in snippet contents
+ Then I should see "line four" in results
+ And I should see "line seven" in results
+ And I should see "line ten" in results
+ And I should not see "line three" in results
+ And I should not see "line eleven" in results
diff --git a/features/snippets/discover.feature b/features/snippets/discover.feature
index f0b8d3a408..1a7e132ea2 100644
--- a/features/snippets/discover.feature
+++ b/features/snippets/discover.feature
@@ -1,11 +1,13 @@
@snippets
-Feature: Discover Snippets
+Feature: Snippets Discover
Background:
Given I sign in as a user
And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet
+ And I have internal "Personal snippet internal" snippet
Scenario: I should see snippets
Given I visit snippets page
Then I should see "Personal snippet one" in snippets
+ And I should see "Personal snippet internal" in snippets
And I should not see "Personal snippet private" in snippets
diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature
new file mode 100644
index 0000000000..c2afb63b6d
--- /dev/null
+++ b/features/snippets/public_snippets.feature
@@ -0,0 +1,10 @@
+Feature: Public snippets
+ Scenario: Unauthenticated user should see public snippets
+ Given There is public "Personal snippet one" snippet
+ And I visit snippet page "Personal snippet one"
+ Then I should see snippet "Personal snippet one"
+
+ Scenario: Unauthenticated user should see raw public snippets
+ Given There is public "Personal snippet one" snippet
+ And I visit snippet raw page "Personal snippet one"
+ Then I should see raw snippet "Personal snippet one"
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
index 38216dd5b7..6e8019c326 100644
--- a/features/snippets/snippets.feature
+++ b/features/snippets/snippets.feature
@@ -1,5 +1,5 @@
@snippets
-Feature: Snippets Feature
+Feature: Snippets
Background:
Given I sign in as a user
And I have public "Personal snippet one" snippet
@@ -25,4 +25,4 @@ Feature: Snippets Feature
Scenario: I destroy "Personal snippet one"
Given I visit snippet page "Personal snippet one"
And I click link "Destroy"
- Then I should not see "Personal snippet one" in snippets
+ Then I should not see "Personal snippet one" in snippets
\ No newline at end of file
diff --git a/features/snippets/user.feature b/features/snippets/user.feature
index d032a33686..5b5dadb7b3 100644
--- a/features/snippets/user.feature
+++ b/features/snippets/user.feature
@@ -1,19 +1,22 @@
@snippets
-Feature: User Snippets
+Feature: Snippets User
Background:
Given I sign in as a user
And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet
+ And I have internal "Personal snippet internal" snippet
Scenario: I should see all my snippets
Given I visit my snippets page
Then I should see "Personal snippet one" in snippets
And I should see "Personal snippet private" in snippets
+ And I should see "Personal snippet internal" in snippets
Scenario: I can see only my private snippets
Given I visit my snippets page
And I click "Private" filter
Then I should not see "Personal snippet one" in snippets
+ And I should not see "Personal snippet internal" in snippets
And I should see "Personal snippet private" in snippets
Scenario: I can see only my public snippets
@@ -21,3 +24,11 @@ Feature: User Snippets
And I click "Public" filter
Then I should see "Personal snippet one" in snippets
And I should not see "Personal snippet private" in snippets
+ And I should not see "Personal snippet internal" in snippets
+
+ Scenario: I can see only my internal snippets
+ Given I visit my snippets page
+ And I click "Internal" filter
+ Then I should see "Personal snippet internal" in snippets
+ And I should not see "Personal snippet private" in snippets
+ And I should not see "Personal snippet one" in snippets
diff --git a/features/steps/admin/active_tab.rb b/features/steps/admin/active_tab.rb
index 8f09e51cce..90d13abdb1 100644
--- a/features/steps/admin/active_tab.rb
+++ b/features/steps/admin/active_tab.rb
@@ -1,37 +1,37 @@
-class AdminActiveTab < Spinach::FeatureSteps
+class Spinach::Features::AdminActiveTab < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedActiveTab
- Then 'the active main tab should be Home' do
+ step 'the active main tab should be Home' do
ensure_active_main_tab('Overview')
end
- Then 'the active main tab should be Projects' do
+ step 'the active main tab should be Projects' do
ensure_active_main_tab('Projects')
end
- Then 'the active main tab should be Groups' do
+ step 'the active main tab should be Groups' do
ensure_active_main_tab('Groups')
end
- Then 'the active main tab should be Users' do
+ step 'the active main tab should be Users' do
ensure_active_main_tab('Users')
end
- Then 'the active main tab should be Logs' do
+ step 'the active main tab should be Logs' do
ensure_active_main_tab('Logs')
end
- Then 'the active main tab should be Hooks' do
+ step 'the active main tab should be Hooks' do
ensure_active_main_tab('Hooks')
end
- Then 'the active main tab should be Resque' do
+ step 'the active main tab should be Resque' do
ensure_active_main_tab('Background Jobs')
end
- Then 'the active main tab should be Messages' do
+ step 'the active main tab should be Messages' do
ensure_active_main_tab('Messages')
end
end
diff --git a/features/steps/admin/applications.rb b/features/steps/admin/applications.rb
new file mode 100644
index 0000000000..d59088fa3c
--- /dev/null
+++ b/features/steps/admin/applications.rb
@@ -0,0 +1,55 @@
+class Spinach::Features::AdminApplications < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+
+ step 'I click on new application button' do
+ click_on 'New Application'
+ end
+
+ step 'I should see application form' do
+ page.should have_content "New application"
+ end
+
+ step 'I fill application form out and submit' do
+ fill_in :doorkeeper_application_name, with: 'test'
+ fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
+ click_on "Submit"
+ end
+
+ step 'I see application' do
+ page.should have_content "Application: test"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click edit' do
+ click_on "Edit"
+ end
+
+ step 'I see edit application form' do
+ page.should have_content "Edit application"
+ end
+
+ step 'I change name of application and submit' do
+ page.should have_content "Edit application"
+ fill_in :doorkeeper_application_name, with: 'test_changed'
+ click_on "Submit"
+ end
+
+ step 'I see that application was changed' do
+ page.should have_content "test_changed"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click to remove application' do
+ within '.oauth-applications' do
+ click_on "Destroy"
+ end
+ end
+
+ step "I see that application is removed" do
+ page.find(".oauth-applications").should_not have_content "test_changed"
+ end
+end
diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb
new file mode 100644
index 0000000000..fb0b611762
--- /dev/null
+++ b/features/steps/admin/deploy_keys.rb
@@ -0,0 +1,57 @@
+class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+
+ step 'there are public deploy keys in system' do
+ create(:deploy_key, public: true)
+ create(:another_deploy_key, public: true)
+ end
+
+ step 'I should see all public deploy keys' do
+ DeployKey.are_public.each do |p|
+ page.should have_content p.title
+ end
+ end
+
+ step 'I click on first deploy key' do
+ click_link DeployKey.are_public.first.title
+ end
+
+ step 'I should see deploy key details' do
+ deploy_key = DeployKey.are_public.first
+ current_path.should == admin_deploy_key_path(deploy_key)
+ page.should have_content(deploy_key.title)
+ page.should have_content(deploy_key.key)
+ end
+
+ step 'I visit admin deploy key page' do
+ visit admin_deploy_key_path(deploy_key)
+ end
+
+ step 'I visit admin deploy keys page' do
+ visit admin_deploy_keys_path
+ end
+
+ step 'I click \'New Deploy Key\'' do
+ click_link 'New Deploy Key'
+ end
+
+ step 'I submit new deploy key' do
+ fill_in "deploy_key_title", with: "laptop"
+ fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
+ click_button "Create"
+ end
+
+ step 'I should be on admin deploy keys page' do
+ current_path.should == admin_deploy_keys_path
+ end
+
+ step 'I should see newly created deploy key' do
+ page.should have_content(deploy_key.title)
+ end
+
+ def deploy_key
+ @deploy_key ||= DeployKey.are_public.first
+ end
+end
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index 9c1bcfefb9..721460b937 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -1,4 +1,4 @@
-class AdminGroups < Spinach::FeatureSteps
+class Spinach::Features::AdminGroups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedUser
@@ -13,7 +13,7 @@ class AdminGroups < Spinach::FeatureSteps
click_link "New Group"
end
- And 'I have group with projects' do
+ step 'I have group with projects' do
@group = create(:group)
@project = create(:project, group: @group)
@event = create(:closed_issue_event, project: @project)
@@ -21,31 +21,30 @@ class AdminGroups < Spinach::FeatureSteps
@project.team << [current_user, :master]
end
- And 'submit form with new group info' do
- fill_in 'group_name', with: 'gitlab'
+ step 'submit form with new group info' do
+ fill_in 'group_path', with: 'gitlab'
fill_in 'group_description', with: 'Group description'
click_button "Create group"
end
- Then 'I should see newly created group' do
+ step 'I should see newly created group' do
page.should have_content "Group: gitlab"
page.should have_content "Group description"
end
- Then 'I should be redirected to group page' do
- current_path.should == admin_group_path(Group.last)
+ step 'I should be redirected to group page' do
+ current_path.should == admin_group_path(Group.find_by(path: 'gitlab'))
end
When 'I select user "John Doe" from user list as "Reporter"' do
- user = User.find_by(name: "John Doe")
- select2(user.id, from: "#user_ids", multiple: true)
- within "#new_team_member" do
- select "Reporter", from: "group_access"
+ select2(user_john.id, from: "#user_ids", multiple: true)
+ within "#new_project_member" do
+ select "Reporter", from: "access_level"
end
- click_button "Add users into group"
+ click_button "Add users to group"
end
- Then 'I should see "John Doe" in team list in every project as "Reporter"' do
+ step 'I should see "John Doe" in team list in every project as "Reporter"' do
within ".group-users-list" do
page.should have_content "John Doe"
page.should have_content "Reporter"
@@ -58,9 +57,29 @@ class AdminGroups < Spinach::FeatureSteps
end
end
+ step 'we have user "John Doe" in group' do
+ current_group.add_user(user_john, Gitlab::Access::REPORTER)
+ end
+
+ step 'I remove user "John Doe" from group' do
+ within "#user_#{user_john.id}" do
+ click_link 'Remove user from group'
+ end
+ end
+
+ step 'I should not see "John Doe" in team list' do
+ within ".group-users-list" do
+ page.should_not have_content "John Doe"
+ end
+ end
+
protected
def current_group
@group ||= Group.first
end
+
+ def user_john
+ @user_john ||= User.find_by(name: "John Doe")
+ end
end
diff --git a/features/steps/admin/logs.rb b/features/steps/admin/logs.rb
index 83958545c4..904e546865 100644
--- a/features/steps/admin/logs.rb
+++ b/features/steps/admin/logs.rb
@@ -1,9 +1,9 @@
-class AdminLogs < Spinach::FeatureSteps
+class Spinach::Features::AdminLogs < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
- Then 'I should see tabs with available logs' do
+ step 'I should see tabs with available logs' do
page.should have_content 'production.log'
page.should have_content 'githost.log'
page.should have_content 'application.log'
diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb
index 992aa46a8b..9be4d39d2d 100644
--- a/features/steps/admin/projects.rb
+++ b/features/steps/admin/projects.rb
@@ -1,31 +1,31 @@
-class AdminProjects < Spinach::FeatureSteps
+class Spinach::Features::AdminProjects < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
- And 'I should see all projects' do
+ step 'I should see all projects' do
Project.all.each do |p|
page.should have_content p.name_with_namespace
end
end
- And 'I click on first project' do
+ step 'I click on first project' do
click_link Project.first.name_with_namespace
end
- Then 'I should see project details' do
+ step 'I should see project details' do
project = Project.first
- current_path.should == admin_project_path(project)
+ current_path.should == admin_namespace_project_path(project.namespace, project)
page.should have_content(project.name_with_namespace)
page.should have_content(project.creator.name)
end
step 'I visit admin project page' do
- visit admin_project_path(project)
+ visit admin_namespace_project_path(project.namespace, project)
end
step 'I transfer project to group \'Web\'' do
- find(:xpath, "//input[@id='namespace_id']").set group.id
+ find(:xpath, "//input[@id='new_namespace_id']").set group.id
click_button 'Transfer'
end
diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb
new file mode 100644
index 0000000000..87d4e969ff
--- /dev/null
+++ b/features/steps/admin/settings.rb
@@ -0,0 +1,47 @@
+class Spinach::Features::AdminSettings < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+ include Gitlab::CurrentSettings
+
+ step 'I modify settings and save form' do
+ uncheck 'Gravatar enabled'
+ fill_in 'Home page url', with: 'https://about.gitlab.com/'
+ click_button 'Save'
+ end
+
+ step 'I should see application settings saved' do
+ current_application_settings.gravatar_enabled.should be_false
+ current_application_settings.home_page_url.should == 'https://about.gitlab.com/'
+ page.should have_content 'Application settings saved successfully'
+ end
+
+ step 'I click on "Service Templates"' do
+ click_link 'Service Templates'
+ end
+
+ step 'I click on "Slack" service' do
+ click_link 'Slack'
+ end
+
+ step 'I check all events and submit form' do
+ page.check('Active')
+ page.check('Push events')
+ page.check('Tag push events')
+ page.check('Comments')
+ page.check('Issues events')
+ page.check('Merge Request events')
+ fill_in 'Webhook', with: "http://localhost"
+ click_on 'Save'
+ end
+
+ step 'I should see service template settings saved' do
+ page.should have_content 'Application settings saved successfully'
+ end
+
+ step 'I should see all checkboxes checked' do
+ all('input[type=checkbox]').each do |checkbox|
+ checkbox.should be_checked
+ end
+ end
+end
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index 253c4609e8..e138309724 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -1,34 +1,34 @@
-class AdminUsers < Spinach::FeatureSteps
+class Spinach::Features::AdminUsers < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
- Then 'I should see all users' do
+ step 'I should see all users' do
User.all.each do |user|
page.should have_content user.name
end
end
- And 'Click edit' do
+ step 'Click edit' do
@user = User.first
find("#edit_user_#{@user.id}").click
end
- And 'Input non ascii char in username' do
+ step 'Input non ascii char in username' do
fill_in 'user_username', with: "\u3042\u3044"
end
- And 'Click save' do
+ step 'Click save' do
click_button("Save")
end
- Then 'See username error message' do
+ step 'See username error message' do
within "#error_explanation" do
page.should have_content "Username"
end
end
- And 'Not changed form action url' do
+ step 'Not changed form action url' do
page.should have_selector %(form[action="/admin/users/#{@user.username}"])
end
@@ -63,4 +63,55 @@ class AdminUsers < Spinach::FeatureSteps
step 'I should not see secondary email anymore' do
page.should_not have_content "Secondary email:"
end
+
+ step 'user "Mike" with groups and projects' do
+ user = create(:user, name: 'Mike')
+
+ project = create(:empty_project)
+ project.team << [user, :developer]
+
+ group = create(:group)
+ group.add_user(user, Gitlab::Access::DEVELOPER)
+ end
+
+ step 'click on "Mike" link' do
+ click_link "Mike"
+ end
+
+ step 'I should see user "Mike" details' do
+ page.should have_content 'Account'
+ page.should have_content 'Personal projects limit'
+ end
+
+ step 'user "Pete" with ssh keys' do
+ user = create(:user, name: 'Pete')
+ create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
+ create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
+ end
+
+ step 'click on user "Pete"' do
+ click_link 'Pete'
+ end
+
+ step 'I should see key list' do
+ page.should have_content 'ssh-rsa Key2'
+ page.should have_content 'ssh-rsa Key1'
+ end
+
+ step 'I click on the key title' do
+ click_link 'ssh-rsa Key2'
+ end
+
+ step 'I should see key details' do
+ page.should have_content 'ssh-rsa Key2'
+ page.should have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2'
+ end
+
+ step 'I click on remove key' do
+ click_link 'Remove'
+ end
+
+ step 'I should see the key removed' do
+ page.should_not have_content 'ssh-rsa Key2'
+ end
end
diff --git a/features/steps/dashboard/active_tab.rb b/features/steps/dashboard/active_tab.rb
index 68d32ed971..0e2c04fb29 100644
--- a/features/steps/dashboard/active_tab.rb
+++ b/features/steps/dashboard/active_tab.rb
@@ -1,21 +1,9 @@
-class DashboardActiveTab < Spinach::FeatureSteps
+class Spinach::Features::DashboardActiveTab < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedActiveTab
- Then 'the active main tab should be Home' do
- ensure_active_main_tab('Activity')
- end
-
- Then 'the active main tab should be Issues' do
- ensure_active_main_tab('Issues')
- end
-
- Then 'the active main tab should be Merge Requests' do
- ensure_active_main_tab('Merge Requests')
- end
-
- Then 'the active main tab should be Help' do
+ step 'the active main tab should be Help' do
ensure_active_main_tab('Help')
end
end
diff --git a/features/steps/dashboard/with_archived_projects.rb b/features/steps/dashboard/archived_projects.rb
similarity index 61%
rename from features/steps/dashboard/with_archived_projects.rb
rename to features/steps/dashboard/archived_projects.rb
index 1bc69555b5..969baf9228 100644
--- a/features/steps/dashboard/with_archived_projects.rb
+++ b/features/steps/dashboard/archived_projects.rb
@@ -1,4 +1,4 @@
-class DashboardWithArchivedProjects < Spinach::FeatureSteps
+class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
@@ -8,15 +8,15 @@ class DashboardWithArchivedProjects < Spinach::FeatureSteps
project.update_attribute(:archived, true)
end
- Then 'I should see "Shop" project link' do
+ step 'I should see "Shop" project link' do
page.should have_link "Shop"
end
- Then 'I should not see "Forum" project link' do
+ step 'I should not see "Forum" project link' do
page.should_not have_link "Forum"
end
- Then 'I should see "Forum" project link' do
+ step 'I should see "Forum" project link' do
page.should have_link "Forum"
end
end
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 84a480bd7f..8508b2a809 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -1,33 +1,33 @@
-class Dashboard < Spinach::FeatureSteps
+class Spinach::Features::Dashboard < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
- Then 'I should see "New Project" link' do
+ step 'I should see "New Project" link' do
page.should have_link "New project"
end
- Then 'I should see "Shop" project link' do
+ step 'I should see "Shop" project link' do
page.should have_link "Shop"
end
- Then 'I should see last push widget' do
+ step 'I should see last push widget' do
page.should have_content "You pushed to fix"
page.should have_link "Create Merge Request"
end
- And 'I click "Create Merge Request" link' do
+ step 'I click "Create Merge Request" link' do
click_link "Create Merge Request"
end
- Then 'I see prefilled new Merge Request page' do
- current_path.should == new_project_merge_request_path(@project)
+ step 'I see prefilled new Merge Request page' do
+ current_path.should == new_namespace_project_merge_request_path(@project.namespace, @project)
find("#merge_request_target_project_id").value.should == @project.id.to_s
find("#merge_request_source_branch").value.should == "fix"
find("#merge_request_target_branch").value.should == "master"
end
- Given 'user with name "John Doe" joined project "Shop"' do
+ step 'user with name "John Doe" joined project "Shop"' do
user = create(:user, {name: "John Doe"})
project.team << [user, :master]
Event.create(
@@ -37,11 +37,11 @@ class Dashboard < Spinach::FeatureSteps
)
end
- Then 'I should see "John Doe joined project at Shop" event' do
- page.should have_content "John Doe joined project at #{project.name_with_namespace}"
+ step 'I should see "John Doe joined project Shop" event' do
+ page.should have_content "John Doe joined project #{project.name_with_namespace}"
end
- And 'user with name "John Doe" left project "Shop"' do
+ step 'user with name "John Doe" left project "Shop"' do
user = User.find_by(name: "John Doe")
Event.create(
project: project,
@@ -50,11 +50,11 @@ class Dashboard < Spinach::FeatureSteps
)
end
- Then 'I should see "John Doe left project at Shop" event' do
- page.should have_content "John Doe left project at #{project.name_with_namespace}"
+ step 'I should see "John Doe left project Shop" event' do
+ page.should have_content "John Doe left project #{project.name_with_namespace}"
end
- And 'I have group with projects' do
+ step 'I have group with projects' do
@group = create(:group)
@project = create(:project, namespace: @group)
@event = create(:closed_issue_event, project: @project)
@@ -62,28 +62,24 @@ class Dashboard < Spinach::FeatureSteps
@project.team << [current_user, :master]
end
- Then 'I should see projects list' do
+ step 'I should see projects list' do
@user.authorized_projects.all.each do |project|
page.should have_link project.name_with_namespace
end
end
- Then 'I should see groups list' do
+ step 'I should see groups list' do
Group.all.each do |group|
page.should have_link group.name
end
end
- And 'group has a projects that does not belongs to me' do
+ step 'group has a projects that does not belongs to me' do
@forbidden_project1 = create(:project, group: @group)
@forbidden_project2 = create(:project, group: @group)
end
- Then 'I should see 1 project at group list' do
- page.find('span.last_activity/span').should have_content('1')
- end
-
- def project
- @project ||= Project.find_by(name: "Shop")
+ step 'I should see 1 project at group list' do
+ find('span.last_activity/span').should have_content('1')
end
end
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index d0fe5c9b64..3da3d62d0c 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -1,35 +1,35 @@
-class EventFilters < Spinach::FeatureSteps
+class Spinach::Features::EventFilters < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
- Then 'I should see push event' do
+ step 'I should see push event' do
page.should have_selector('span.pushed')
end
- Then 'I should not see push event' do
+ step 'I should not see push event' do
page.should_not have_selector('span.pushed')
end
- Then 'I should see new member event' do
+ step 'I should see new member event' do
page.should have_selector('span.joined')
end
- And 'I should not see new member event' do
+ step 'I should not see new member event' do
page.should_not have_selector('span.joined')
end
- Then 'I should see merge request event' do
+ step 'I should see merge request event' do
page.should have_selector('span.accepted')
end
- And 'I should not see merge request event' do
+ step 'I should not see merge request event' do
page.should_not have_selector('span.accepted')
end
- And 'this project has push event' do
+ step 'this project has push event' do
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/new_design",
user_id: @user.id,
@@ -51,7 +51,7 @@ class EventFilters < Spinach::FeatureSteps
)
end
- And 'this project has new member event' do
+ step 'this project has new member event' do
user = create(:user, {name: "John Doe"})
Event.create(
project: @project,
@@ -60,7 +60,7 @@ class EventFilters < Spinach::FeatureSteps
)
end
- And 'this project has merge request event' do
+ step 'this project has merge request event' do
merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project
Event.create(
project: @project,
@@ -82,6 +82,4 @@ class EventFilters < Spinach::FeatureSteps
When 'I click "merge" event filter' do
click_link("merged_event_filter")
end
-
end
-
diff --git a/features/steps/profile/group.rb b/features/steps/dashboard/group.rb
similarity index 57%
rename from features/steps/profile/group.rb
rename to features/steps/dashboard/group.rb
index 03144104c7..8384df2fb5 100644
--- a/features/steps/profile/group.rb
+++ b/features/steps/dashboard/group.rb
@@ -1,4 +1,4 @@
-class ProfileGroup < Spinach::FeatureSteps
+class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
include SharedAuthentication
include SharedGroup
include SharedPaths
@@ -7,22 +7,22 @@ class ProfileGroup < Spinach::FeatureSteps
# Leave
step 'I click on the "Leave" button for group "Owned"' do
- find(:css, 'li', text: "Owner").find(:css, 'i.icon-signout').click
+ find(:css, 'li', text: "Owner").find(:css, 'i.fa.fa-sign-out').click
# poltergeist always confirms popups.
end
step 'I click on the "Leave" button for group "Guest"' do
- find(:css, 'li', text: "Guest").find(:css, 'i.icon-signout').click
+ find(:css, 'li', text: "Guest").find(:css, 'i.fa.fa-sign-out').click
# poltergeist always confirms popups.
end
step 'I should not see the "Leave" button for group "Owned"' do
- find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.icon-signout')
+ find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.fa.fa-sign-out')
# poltergeist always confirms popups.
end
step 'I should not see the "Leave" button for groupr "Guest"' do
- find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.icon-signout')
+ find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.fa.fa-sign-out')
# poltergeist always confirms popups.
end
@@ -41,4 +41,23 @@ class ProfileGroup < Spinach::FeatureSteps
step 'I should not see group "Guest" in group list' do
page.should_not have_content("Guest")
end
+
+ step 'I click new group link' do
+ click_link "New Group"
+ end
+
+ step 'submit form with new group "Samurai" info' do
+ fill_in 'group_path', with: 'Samurai'
+ fill_in 'group_description', with: 'Tokugawa Shogunate'
+ click_button "Create group"
+ end
+
+ step 'I should be redirected to group "Samurai" page' do
+ current_path.should == group_path(Group.find_by(name: 'Samurai'))
+ end
+
+ step 'I should see newly created group "Samurai"' do
+ page.should have_content "Samurai"
+ page.should have_content "Tokugawa Shogunate"
+ end
end
diff --git a/features/steps/help.rb b/features/steps/dashboard/help.rb
similarity index 90%
rename from features/steps/help.rb
rename to features/steps/dashboard/help.rb
index 0d1c9c0037..ef433c57c6 100644
--- a/features/steps/help.rb
+++ b/features/steps/dashboard/help.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::Help < Spinach::FeatureSteps
+class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedMarkdown
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 1344edfa80..60da36e86d 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -1,6 +1,7 @@
-class DashboardIssues < Spinach::FeatureSteps
+class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include Select2Helper
step 'I should see issues assigned to me' do
should_see(assigned_issue)
@@ -10,6 +11,7 @@ class DashboardIssues < Spinach::FeatureSteps
step 'I should see issues authored by me' do
should_see(authored_issue)
+ should_see(authored_issue_on_public_project)
should_not_see(assigned_issue)
should_not_see(other_issue)
end
@@ -22,6 +24,7 @@ class DashboardIssues < Spinach::FeatureSteps
step 'I have authored issues' do
authored_issue
+ authored_issue_on_public_project
end
step 'I have assigned issues' do
@@ -33,15 +36,13 @@ class DashboardIssues < Spinach::FeatureSteps
end
step 'I click "Authored by me" link' do
- within ".scope-filter" do
- click_link 'Created by me'
- end
+ select2(current_user.id, from: "#author_id")
+ select2(nil, from: "#assignee_id")
end
step 'I click "All" link' do
- within ".scope-filter" do
- click_link "Everyone's"
- end
+ select2(nil, from: "#author_id")
+ select2(nil, from: "#assignee_id")
end
def should_see(issue)
@@ -64,6 +65,10 @@ class DashboardIssues < Spinach::FeatureSteps
@other_issue ||= create :issue, project: project
end
+ def authored_issue_on_public_project
+ @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project
+ end
+
def project
@project ||= begin
project =create :project
@@ -71,4 +76,8 @@ class DashboardIssues < Spinach::FeatureSteps
project
end
end
+
+ def public_project
+ @public_project ||= create :project, :public
+ end
end
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index e198bc0cf9..9d92082bb8 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -1,16 +1,21 @@
-class DashboardMergeRequests < Spinach::FeatureSteps
+class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include Select2Helper
step 'I should see merge requests assigned to me' do
should_see(assigned_merge_request)
+ should_see(assigned_merge_request_from_fork)
should_not_see(authored_merge_request)
+ should_not_see(authored_merge_request_from_fork)
should_not_see(other_merge_request)
end
step 'I should see merge requests authored by me' do
should_see(authored_merge_request)
+ should_see(authored_merge_request_from_fork)
should_not_see(assigned_merge_request)
+ should_not_see(assigned_merge_request_from_fork)
should_not_see(other_merge_request)
end
@@ -22,10 +27,12 @@ class DashboardMergeRequests < Spinach::FeatureSteps
step 'I have authored merge requests' do
authored_merge_request
+ authored_merge_request_from_fork
end
step 'I have assigned merge requests' do
assigned_merge_request
+ assigned_merge_request_from_fork
end
step 'I have other merge requests' do
@@ -33,15 +40,13 @@ class DashboardMergeRequests < Spinach::FeatureSteps
end
step 'I click "Authored by me" link' do
- within ".scope-filter" do
- click_link 'Created by me'
- end
+ select2(current_user.id, from: "#author_id")
+ select2(nil, from: "#assignee_id")
end
step 'I click "All" link' do
- within ".scope-filter" do
- click_link "Everyone's"
- end
+ select2(nil, from: "#author_id")
+ select2(nil, from: "#assignee_id")
end
def should_see(merge_request)
@@ -53,15 +58,41 @@ class DashboardMergeRequests < Spinach::FeatureSteps
end
def assigned_merge_request
- @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
+ @assigned_merge_request ||= create :merge_request,
+ assignee: current_user,
+ target_project: project,
+ source_project: project
end
def authored_merge_request
- @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
+ @authored_merge_request ||= create :merge_request,
+ source_branch: 'simple_merge_request',
+ author: current_user,
+ target_project: project,
+ source_project: project
end
def other_merge_request
- @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
+ @other_merge_request ||= create :merge_request,
+ source_branch: '2_3_notes_fix',
+ target_project: project,
+ source_project: project
+ end
+
+ def authored_merge_request_from_fork
+ @authored_merge_request_from_fork ||= create :merge_request,
+ source_branch: 'basic_page',
+ author: current_user,
+ target_project: public_project,
+ source_project: forked_project
+ end
+
+ def assigned_merge_request_from_fork
+ @assigned_merge_request_from_fork ||= create :merge_request,
+ source_branch: 'basic_page_fix',
+ assignee: current_user,
+ target_project: public_project,
+ source_project: forked_project
end
def project
@@ -71,4 +102,12 @@ class DashboardMergeRequests < Spinach::FeatureSteps
project
end
end
+
+ def public_project
+ @public_project ||= create :project, :public
+ end
+
+ def forked_project
+ @forked_project ||= Projects::ForkService.new(public_project, current_user).execute
+ end
end
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
new file mode 100644
index 0000000000..5e588ceb78
--- /dev/null
+++ b/features/steps/dashboard/new_project.rb
@@ -0,0 +1,27 @@
+class Spinach::Features::NewProject < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ step 'I click "New project" link' do
+ click_link "New project"
+ end
+
+ step 'I see "New project" page' do
+ page.should have_content("Project path")
+ end
+
+ step 'I click on "Import project from GitHub"' do
+ first('.how_to_import_link').click
+ end
+
+ step 'I see instructions on how to import from GitHub' do
+ github_modal = first('.modal-body')
+ github_modal.should be_visible
+ github_modal.should have_content "To enable importing projects from GitHub"
+
+ all('.modal-body').each do |element|
+ element.should_not be_visible unless element == github_modal
+ end
+ end
+end
diff --git a/features/steps/dashboard/projects.rb b/features/steps/dashboard/projects.rb
deleted file mode 100644
index 8525156544..0000000000
--- a/features/steps/dashboard/projects.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class DashboardProjects < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- Then 'I should see projects list' do
- @user.authorized_projects.all.each do |project|
- page.should have_link project.name_with_namespace
- end
- end
-end
diff --git a/features/steps/dashboard/search.rb b/features/steps/dashboard/search.rb
deleted file mode 100644
index 32966a8617..0000000000
--- a/features/steps/dashboard/search.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class DashboardSearch < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- Given 'I search for "Sho"' do
- fill_in "dashboard_search", with: "Sho"
- click_button "Search"
- end
-
- Then 'I should see "Shop" project link' do
- page.should have_link "Shop"
- end
-
- Given 'I search for "Contibuting"' do
- fill_in "dashboard_search", with: "Contibuting"
- click_button "Search"
- end
-end
diff --git a/features/steps/dashboard/shortcuts.rb b/features/steps/dashboard/shortcuts.rb
new file mode 100644
index 0000000000..a9083850b5
--- /dev/null
+++ b/features/steps/dashboard/shortcuts.rb
@@ -0,0 +1,6 @@
+class Spinach::Features::DashboardShortcuts < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+ include SharedActiveTab
+end
diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb
new file mode 100644
index 0000000000..b9ad2f13e2
--- /dev/null
+++ b/features/steps/dashboard/starred_projects.rb
@@ -0,0 +1,15 @@
+class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ step 'I starred project "Community"' do
+ current_user.toggle_star(Project.find_by(name: 'Community'))
+ end
+
+ step 'I should not see project "Shop"' do
+ within 'aside' do
+ page.should_not have_content('Shop')
+ end
+ end
+end
diff --git a/features/steps/explore/groups_feature.rb b/features/steps/explore/groups.rb
similarity index 92%
rename from features/steps/explore/groups_feature.rb
rename to features/steps/explore/groups.rb
index b529c5f845..0c2127d4c4 100644
--- a/features/steps/explore/groups_feature.rb
+++ b/features/steps/explore/groups.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ExploreGroupsFeature < Spinach::FeatureSteps
+class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedGroup
@@ -35,7 +35,7 @@ class Spinach::Features::ExploreGroupsFeature < Spinach::FeatureSteps
end
step 'I visit group "TestGroup" members page' do
- visit members_group_path(Group.find_by(name: "TestGroup"))
+ visit group_group_members_path(Group.find_by(name: "TestGroup"))
end
step 'I should not see project "Enterprise" items' do
@@ -63,7 +63,7 @@ class Spinach::Features::ExploreGroupsFeature < Spinach::FeatureSteps
end
step 'I should not see member roles' do
- page.body.should_not match(%r{owner|developer|reporter|guest}i)
+ body.should_not match(%r{owner|developer|reporter|guest}i)
end
protected
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index dfd5106073..26b71406bd 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps
+class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
@@ -22,26 +22,26 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps
step 'I should see empty public project details with http clone info' do
project = Project.find_by(name: 'Empty Public Project')
- page.all(:css, '.git-empty .clone').each do |element|
+ all(:css, '.git-empty .clone').each do |element|
element.text.should include(project.http_url_to_repo)
end
end
step 'I should see empty public project details with ssh clone info' do
project = Project.find_by(name: 'Empty Public Project')
- page.all(:css, '.git-empty .clone').each do |element|
+ all(:css, '.git-empty .clone').each do |element|
element.text.should include(project.url_to_repo)
end
end
step 'I should see project "Community" home page' do
- within '.project-home-title' do
+ within '.navbar-gitlab .title' do
page.should have_content 'Community'
end
end
step 'I should see project "Internal" home page' do
- within '.project-home-title' do
+ within '.navbar-gitlab .title' do
page.should have_content 'Internal'
end
end
@@ -65,7 +65,7 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps
title: "New feature",
project: public_project
)
- visit project_issues_path(public_project)
+ visit namespace_project_issues_path(public_project.namespace, public_project)
end
@@ -84,7 +84,7 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps
title: "New internal feature",
project: internal_project
)
- visit project_issues_path(internal_project)
+ visit namespace_project_issues_path(internal_project.namespace, internal_project)
end
@@ -95,7 +95,7 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps
end
step 'I visit "Community" merge requests page' do
- visit project_merge_requests_path(public_project)
+ visit namespace_project_merge_requests_path(public_project.namespace, public_project)
end
step 'project "Community" has "Bug fix" open merge request' do
@@ -112,7 +112,7 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps
end
step 'I visit "Internal" merge requests page' do
- visit project_merge_requests_path(internal_project)
+ visit namespace_project_merge_requests_path(internal_project.namespace, internal_project)
end
step 'project "Internal" has "Feature implemented" open merge request' do
diff --git a/features/steps/group/group.rb b/features/steps/groups.rb
similarity index 75%
rename from features/steps/group/group.rb
rename to features/steps/groups.rb
index c3ee42f112..228b83e5fd 100644
--- a/features/steps/group/group.rb
+++ b/features/steps/groups.rb
@@ -1,70 +1,113 @@
-class Groups < Spinach::FeatureSteps
+class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedGroup
include SharedUser
include Select2Helper
- Then 'I should see group "Owned" projects list' do
+ step 'gitlab user "Mike"' do
+ create(:user, name: "Mike")
+ end
+
+ step 'I click link "Add members"' do
+ find(:css, 'button.btn-new').click
+ end
+
+ step 'I select "Mike" as "Reporter"' do
+ user = User.find_by(name: "Mike")
+
+ within ".users-group-form" do
+ select2(user.id, from: "#user_ids", multiple: true)
+ select "Reporter", from: "access_level"
+ end
+
+ click_button "Add users to group"
+ end
+
+ step 'I should see "Mike" in team list as "Reporter"' do
+ within '.well-list' do
+ page.should have_content('Mike')
+ page.should have_content('Reporter')
+ end
+ end
+
+ step 'I select "sjobs@apple.com" as "Reporter"' do
+ within ".users-group-form" do
+ select2("sjobs@apple.com", from: "#user_ids", multiple: true)
+ select "Reporter", from: "access_level"
+ end
+
+ click_button "Add users to group"
+ end
+
+ step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
+ within '.well-list' do
+ page.should have_content('sjobs@apple.com')
+ page.should have_content('invited')
+ page.should have_content('Reporter')
+ end
+ end
+
+ step 'I should see group "Owned" projects list' do
Group.find_by(name: "Owned").projects.each do |project|
page.should have_link project.name
end
end
- And 'I should see projects activity feed' do
+ step 'I should see projects activity feed' do
page.should have_content 'closed issue'
end
- Then 'I should see issues from group "Owned" assigned to me' do
+ step 'I should see issues from group "Owned" assigned to me' do
assigned_to_me(:issues).each do |issue|
page.should have_content issue.title
end
end
- Then 'I should see merge requests from group "Owned" assigned to me' do
+ step 'I should see merge requests from group "Owned" assigned to me' do
assigned_to_me(:merge_requests).each do |issue|
page.should have_content issue.title[0..80]
end
end
- And 'I select user "Mary Jane" from list with role "Reporter"' do
+ step 'I select user "Mary Jane" from list with role "Reporter"' do
user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
- click_link 'Add members'
+ click_button 'Add members'
within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
- select "Reporter", from: "group_access"
+ select "Reporter", from: "access_level"
end
- click_button "Add users into group"
+ click_button "Add users to group"
end
- Then 'I should see user "John Doe" in team list' do
+ step 'I should see user "John Doe" in team list' do
projects_with_access = find(".panel .well-list")
projects_with_access.should have_content("John Doe")
end
- Then 'I should not see user "John Doe" in team list' do
+ step 'I should not see user "John Doe" in team list' do
projects_with_access = find(".panel .well-list")
projects_with_access.should_not have_content("John Doe")
end
- Then 'I should see user "Mary Jane" in team list' do
+ step 'I should see user "Mary Jane" in team list' do
projects_with_access = find(".panel .well-list")
projects_with_access.should have_content("Mary Jane")
end
- Then 'I should not see user "Mary Jane" in team list' do
+ step 'I should not see user "Mary Jane" in team list' do
projects_with_access = find(".panel .well-list")
projects_with_access.should_not have_content("Mary Jane")
end
- Given 'project from group "Owned" has issues assigned to me' do
+ step 'project from group "Owned" has issues assigned to me' do
create :issue,
project: project,
assignee: current_user,
author: current_user
end
- Given 'project from group "Owned" has merge requests assigned to me' do
+ step 'project from group "Owned" has merge requests assigned to me' do
create :merge_request,
source_project: project,
target_project: project,
@@ -72,34 +115,15 @@ class Groups < Spinach::FeatureSteps
author: current_user
end
- When 'I click new group link' do
- click_link "New group"
- end
-
- And 'submit form with new group "Samurai" info' do
- fill_in 'group_name', with: 'Samurai'
- fill_in 'group_description', with: 'Tokugawa Shogunate'
- click_button "Create group"
- end
-
- Then 'I should be redirected to group "Samurai" page' do
- current_path.should == group_path(Group.last)
- end
-
- Then 'I should see newly created group "Samurai"' do
- page.should have_content "Samurai"
- page.should have_content "Tokugawa Shogunate"
- page.should have_content "Currently you are only seeing events from the"
- end
-
- And 'I change group "Owned" name to "new-name"' do
+ step 'I change group "Owned" name to "new-name"' do
fill_in 'group_name', with: 'new-name'
+ fill_in 'group_path', with: 'new-name'
click_button "Save group"
end
- Then 'I should see new group "Owned" name' do
+ step 'I should see new group "Owned" name' do
within ".navbar-gitlab" do
- page.should have_content "group: new-name"
+ page.should have_content "new-name"
end
end
@@ -110,7 +134,7 @@ class Groups < Spinach::FeatureSteps
end
step 'I should see new group "Owned" avatar' do
- Group.find_by(name: "Owned").avatar.should be_instance_of AttachmentUploader
+ Group.find_by(name: "Owned").avatar.should be_instance_of AvatarUploader
Group.find_by(name: "Owned").avatar.url.should == "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png"
end
@@ -188,20 +212,19 @@ class Groups < Spinach::FeatureSteps
end
step 'I should see group milestone with descriptions and expiry date' do
- page.should have_content('Lorem Ipsum is simply dummy text of the printing and typesetting industry')
page.should have_content('expires at Aug 20, 2114')
end
step 'I should see group milestone with all issues and MRs assigned to that milestone' do
page.should have_content('Milestone GL-113')
page.should have_content('Progress: 0 closed – 4 open')
- page.should have_link(@issue1.title, href: project_issue_path(@project1, @issue1))
- page.should have_link(@mr3.title, href: project_merge_request_path(@project3, @mr3))
+ page.should have_link(@issue1.title, href: namespace_project_issue_path(@project1.namespace, @project1, @issue1))
+ page.should have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3))
end
protected
- def assigned_to_me key
+ def assigned_to_me(key)
project.send(key).where(assignee_id: current_user.id)
end
diff --git a/features/steps/invites.rb b/features/steps/invites.rb
new file mode 100644
index 0000000000..d051cc3edc
--- /dev/null
+++ b/features/steps/invites.rb
@@ -0,0 +1,80 @@
+class Spinach::Features::Invites < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedUser
+ include SharedGroup
+
+ step '"John Doe" has invited "user@example.com" to group "Owned"' do
+ user = User.find_by(name: "John Doe")
+ group = Group.find_by(name: "Owned")
+ group.add_user("user@example.com", Gitlab::Access::DEVELOPER, user)
+ end
+
+ step 'I visit the invitation page' do
+ group = Group.find_by(name: "Owned")
+ invite = group.group_members.invite.last
+ invite.generate_invite_token!
+ @raw_invite_token = invite.raw_invite_token
+ visit invite_path(@raw_invite_token)
+ end
+
+ step 'I should be redirected to the sign in page' do
+ expect(current_path).to eq(new_user_session_path)
+ end
+
+ step 'I should see a notice telling me to sign in' do
+ expect(page).to have_content "To accept this invitation, sign in"
+ end
+
+ step 'I should be redirected to the invitation page' do
+ expect(current_path).to eq(invite_path(@raw_invite_token))
+ end
+
+ step 'I should see the invitation details' do
+ expect(page).to have_content("You have been invited by John Doe to join group Owned as Developer.")
+ end
+
+ step "I should see a message telling me I'm already a member" do
+ expect(page).to have_content("However, you are already a member of this group.")
+ end
+
+ step 'I should see an "Accept invitation" button' do
+ expect(page).to have_link("Accept invitation")
+ end
+
+ step 'I should see a "Decline" button' do
+ expect(page).to have_link("Decline")
+ end
+
+ step 'I click the "Accept invitation" button' do
+ page.click_link "Accept invitation"
+ end
+
+ step 'I should be redirected to the group page' do
+ group = Group.find_by(name: "Owned")
+ expect(current_path).to eq(group_path(group))
+ end
+
+ step 'I should see a notice telling me I have access' do
+ expect(page).to have_content("You have been granted Developer access to group Owned.")
+ end
+
+ step 'I click the "Decline" button' do
+ page.click_link "Decline"
+ end
+
+ step 'I should be redirected to the dashboard' do
+ expect(current_path).to eq(dashboard_path)
+ end
+
+ step 'I should see a notice telling me I have declined' do
+ expect(page).to have_content("You have declined the invitation to join group Owned.")
+ end
+
+ step "I visit the invitation's decline page" do
+ group = Group.find_by(name: "Owned")
+ invite = group.group_members.invite.last
+ invite.generate_invite_token!
+ @raw_invite_token = invite.raw_invite_token
+ visit decline_invite_path(@raw_invite_token)
+ end
+end
diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb
index 1924a6fa78..8595ee876a 100644
--- a/features/steps/profile/active_tab.rb
+++ b/features/steps/profile/active_tab.rb
@@ -1,25 +1,25 @@
-class ProfileActiveTab < Spinach::FeatureSteps
+class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedActiveTab
- Then 'the active main tab should be Home' do
+ step 'the active main tab should be Home' do
ensure_active_main_tab('Profile')
end
- Then 'the active main tab should be Account' do
+ step 'the active main tab should be Account' do
ensure_active_main_tab('Account')
end
- Then 'the active main tab should be SSH Keys' do
+ step 'the active main tab should be SSH Keys' do
ensure_active_main_tab('SSH Keys')
end
- Then 'the active main tab should be Design' do
+ step 'the active main tab should be Design' do
ensure_active_main_tab('Design')
end
- Then 'the active main tab should be History' do
+ step 'the active main tab should be History' do
ensure_active_main_tab('History')
end
end
diff --git a/features/steps/profile/emails.rb b/features/steps/profile/emails.rb
index 99588c8599..2b6ac37d86 100644
--- a/features/steps/profile/emails.rb
+++ b/features/steps/profile/emails.rb
@@ -1,47 +1,47 @@
-class ProfileEmails < Spinach::FeatureSteps
+class Spinach::Features::ProfileEmails < Spinach::FeatureSteps
include SharedAuthentication
- Then 'I visit profile emails page' do
+ step 'I visit profile emails page' do
visit profile_emails_path
end
- Then 'I should see my emails' do
+ step 'I should see my emails' do
page.should have_content(@user.email)
@user.emails.each do |email|
page.should have_content(email.email)
end
end
- And 'I submit new email "my@email.com"' do
+ step 'I submit new email "my@email.com"' do
fill_in "email_email", with: "my@email.com"
click_button "Add"
end
- Then 'I should see new email "my@email.com"' do
+ step 'I should see new email "my@email.com"' do
email = @user.emails.find_by(email: "my@email.com")
email.should_not be_nil
page.should have_content("my@email.com")
end
- Then 'I should not see email "my@email.com"' do
+ step 'I should not see email "my@email.com"' do
email = @user.emails.find_by(email: "my@email.com")
email.should be_nil
page.should_not have_content("my@email.com")
end
- Then 'I click link "Remove" for "my@email.com"' do
+ step 'I click link "Remove" for "my@email.com"' do
# there should only be one remove button at this time
click_link "Remove"
# force these to reload as they have been cached
@user.emails.reload
end
- And 'I submit duplicate email @user.email' do
+ step 'I submit duplicate email @user.email' do
fill_in "email_email", with: @user.email
click_button "Add"
end
- Then 'I should not have @user.email added' do
+ step 'I should not have @user.email added' do
email = @user.emails.find_by(email: @user.email)
email.should be_nil
end
diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb
index e884df3098..13e93618eb 100644
--- a/features/steps/profile/notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -1,4 +1,4 @@
-class ProfileNotifications < Spinach::FeatureSteps
+class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
@@ -7,6 +7,6 @@ class ProfileNotifications < Spinach::FeatureSteps
end
step 'I should see global notifications settings' do
- page.should have_content "Notifications settings"
+ page.should have_content "Notifications Settings"
end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 5a7ac20731..791982d16c 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -1,9 +1,9 @@
-class Profile < Spinach::FeatureSteps
+class Spinach::Features::Profile < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
step 'I should see my profile info' do
- page.should have_content "Profile settings"
+ page.should have_content "Profile Settings"
end
step 'I change my profile info' do
@@ -11,6 +11,7 @@ class Profile < Spinach::FeatureSteps
fill_in "user_linkedin", with: "testlinkedin"
fill_in "user_twitter", with: "testtwitter"
fill_in "user_website_url", with: "testurl"
+ fill_in "user_location", with: "Ukraine"
click_button "Save changes"
@user.reload
end
@@ -20,6 +21,7 @@ class Profile < Spinach::FeatureSteps
@user.linkedin.should == 'testlinkedin'
@user.twitter.should == 'testtwitter'
@user.website_url.should == 'testurl'
+ find("#user_location").value.should == "Ukraine"
end
step 'I change my avatar' do
@@ -29,7 +31,7 @@ class Profile < Spinach::FeatureSteps
end
step 'I should see new avatar' do
- @user.avatar.should be_instance_of AttachmentUploader
+ @user.avatar.should be_instance_of AvatarUploader
@user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png"
end
@@ -136,7 +138,7 @@ class Profile < Spinach::FeatureSteps
end
step "I am not an ldap user" do
- current_user.update_attributes(extern_uid: nil, provider: '')
+ current_user.identities.delete
current_user.ldap_user?.should be_false
end
@@ -187,4 +189,54 @@ class Profile < Spinach::FeatureSteps
step 'I should see groups I belong to' do
page.should have_css('.profile-groups-avatars', visible: true)
end
+
+ step 'I click on new application button' do
+ click_on 'New Application'
+ end
+
+ step 'I should see application form' do
+ page.should have_content "New application"
+ end
+
+ step 'I fill application form out and submit' do
+ fill_in :doorkeeper_application_name, with: 'test'
+ fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
+ click_on "Submit"
+ end
+
+ step 'I see application' do
+ page.should have_content "Application: test"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click edit' do
+ click_on "Edit"
+ end
+
+ step 'I see edit application form' do
+ page.should have_content "Edit application"
+ end
+
+ step 'I change name of application and submit' do
+ page.should have_content "Edit application"
+ fill_in :doorkeeper_application_name, with: 'test_changed'
+ click_on "Submit"
+ end
+
+ step 'I see that application was changed' do
+ page.should have_content "test_changed"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click to remove application' do
+ within '.oauth-applications' do
+ click_on "Destroy"
+ end
+ end
+
+ step "I see that application is removed" do
+ page.find(".oauth-applications").should_not have_content "test_changed"
+ end
end
diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb
index 65ca824bb5..ea912e5b4d 100644
--- a/features/steps/profile/ssh_keys.rb
+++ b/features/steps/profile/ssh_keys.rb
@@ -1,48 +1,46 @@
-class ProfileSshKeys < Spinach::FeatureSteps
+class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
include SharedAuthentication
- Then 'I should see my ssh keys' do
+ step 'I should see my ssh keys' do
@user.keys.each do |key|
page.should have_content(key.title)
end
end
- Given 'I click link "Add new"' do
+ step 'I click link "Add new"' do
click_link "Add SSH Key"
end
- And 'I submit new ssh key "Laptop"' do
+ step 'I submit new ssh key "Laptop"' do
fill_in "key_title", with: "Laptop"
fill_in "key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
click_button "Add key"
end
- Then 'I should see new ssh key "Laptop"' do
+ step 'I should see new ssh key "Laptop"' do
key = Key.find_by(title: "Laptop")
page.should have_content(key.title)
page.should have_content(key.key)
current_path.should == profile_key_path(key)
end
- Given 'I click link "Work"' do
+ step 'I click link "Work"' do
click_link "Work"
end
- And 'I click link "Remove"' do
+ step 'I click link "Remove"' do
click_link "Remove"
end
- Then 'I visit profile keys page' do
+ step 'I visit profile keys page' do
visit profile_keys_path
end
- And 'I should not see "Work" ssh key' do
- within "#keys-table" do
- page.should_not have_content "Work"
- end
+ step 'I should not see "Work" ssh key' do
+ page.should_not have_content "Work"
end
- And 'I have ssh key "ssh-rsa Work"' do
+ step 'I have ssh key "ssh-rsa Work"' do
create(:key, user: @user, title: "ssh-rsa Work", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work")
end
end
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index dfafbc6fc0..dd3215adb1 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -1,140 +1,103 @@
-class ProjectActiveTab < Spinach::FeatureSteps
+class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
include SharedActiveTab
-
- # Main Tabs
-
- Then 'the active main tab should be Home' do
- ensure_active_main_tab('Activity')
- end
-
- Then 'the active main tab should be Settings' do
- ensure_active_main_tab('Settings')
- end
-
- Then 'the active main tab should be Files' do
- ensure_active_main_tab('Files')
- end
-
- Then 'the active main tab should be Commits' do
- ensure_active_main_tab('Commits')
- end
-
- Then 'the active main tab should be Network' do
- ensure_active_main_tab('Network')
- end
-
- Then 'the active main tab should be Issues' do
- ensure_active_main_tab('Issues')
- end
-
- Then 'the active main tab should be Merge Requests' do
- ensure_active_main_tab('Merge Requests')
- end
-
- Then 'the active main tab should be Wall' do
- ensure_active_main_tab('Wall')
- end
-
- Then 'the active main tab should be Wiki' do
- ensure_active_main_tab('Wiki')
- end
+ include SharedProjectTab
# Sub Tabs: Home
- Given 'I click the "Team" tab' do
+ step 'I click the "Team" tab' do
click_link('Members')
end
- Given 'I click the "Attachments" tab' do
+ step 'I click the "Attachments" tab' do
click_link('Attachments')
end
- Given 'I click the "Snippets" tab' do
+ step 'I click the "Snippets" tab' do
click_link('Snippets')
end
- Given 'I click the "Edit" tab' do
+ step 'I click the "Edit" tab' do
within '.project-settings-nav' do
click_link('Project')
end
end
- Given 'I click the "Hooks" tab' do
+ step 'I click the "Hooks" tab' do
click_link('Web Hooks')
end
- Given 'I click the "Deploy Keys" tab' do
+ step 'I click the "Deploy Keys" tab' do
click_link('Deploy Keys')
end
- Then 'the active sub nav should be Team' do
+ step 'the active sub nav should be Team' do
ensure_active_sub_nav('Members')
end
- Then 'the active sub nav should be Edit' do
+ step 'the active sub nav should be Edit' do
ensure_active_sub_nav('Project')
end
- Then 'the active sub nav should be Hooks' do
+ step 'the active sub nav should be Hooks' do
ensure_active_sub_nav('Web Hooks')
end
- Then 'the active sub nav should be Deploy Keys' do
+ step 'the active sub nav should be Deploy Keys' do
ensure_active_sub_nav('Deploy Keys')
end
# Sub Tabs: Commits
- Given 'I click the "Compare" tab' do
+ step 'I click the "Compare" tab' do
click_link('Compare')
end
- Given 'I click the "Branches" tab' do
+ step 'I click the "Branches" tab' do
click_link('Branches')
end
- Given 'I click the "Tags" tab' do
+ step 'I click the "Tags" tab' do
click_link('Tags')
end
- Then 'the active sub tab should be Commits' do
+ step 'the active sub tab should be Commits' do
ensure_active_sub_tab('Commits')
end
- Then 'the active sub tab should be Compare' do
+ step 'the active sub tab should be Compare' do
ensure_active_sub_tab('Compare')
end
- Then 'the active sub tab should be Branches' do
+ step 'the active sub tab should be Branches' do
ensure_active_sub_tab('Branches')
end
- Then 'the active sub tab should be Tags' do
+ step 'the active sub tab should be Tags' do
ensure_active_sub_tab('Tags')
end
# Sub Tabs: Issues
- Given 'I click the "Milestones" tab' do
+ step 'I click the "Milestones" tab' do
click_link('Milestones')
end
- Given 'I click the "Labels" tab' do
+ step 'I click the "Labels" tab' do
click_link('Labels')
end
- Then 'the active sub tab should be Browse Issues' do
- ensure_active_sub_tab('Browse Issues')
+ step 'the active sub tab should be Issues' do
+ ensure_active_sub_tab('Issues')
end
- Then 'the active sub tab should be Milestones' do
- ensure_active_sub_tab('Milestones')
+ step 'the active main tab should be Milestones' do
+ ensure_active_main_tab('Milestones')
end
- Then 'the active sub tab should be Labels' do
- ensure_active_sub_tab('Labels')
+ step 'the active main tab should be Labels' do
+ ensure_active_main_tab('Labels')
end
end
diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb
index 8b490d2ffc..37ad0c7765 100644
--- a/features/steps/project/archived.rb
+++ b/features/steps/project/archived.rb
@@ -1,4 +1,4 @@
-class ProjectArchived < Spinach::FeatureSteps
+class Spinach::Features::ProjectArchived < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
@@ -15,14 +15,14 @@ class ProjectArchived < Spinach::FeatureSteps
When 'I visit project "Forum" page' do
project = Project.find_by(name: "Forum")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
- Then 'I should not see "Archived"' do
+ step 'I should not see "Archived"' do
page.should_not have_content "Archived"
end
- Then 'I should see "Archived"' do
+ step 'I should see "Archived"' do
page.should have_content "Archived"
end
diff --git a/features/steps/project/browse_branches.rb b/features/steps/project/browse_branches.rb
deleted file mode 100644
index 7a0625952d..0000000000
--- a/features/steps/project/browse_branches.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-class ProjectBrowseBranches < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- step 'I click link "All"' do
- click_link "All"
- end
-
- step 'I should see "Shop" all branches list' do
- page.should have_content "Branches"
- page.should have_content "master"
- end
-
- step 'I click link "Protected"' do
- click_link "Protected"
- end
-
- step 'I should see "Shop" protected branches list' do
- within ".protected-branches-list" do
- page.should have_content "stable"
- page.should_not have_content "master"
- end
- end
-
- step 'project "Shop" has protected branches' do
- project = Project.find_by(name: "Shop")
- project.protected_branches.create(name: "stable")
- end
-
- step 'I click new branch link' do
- click_link "New branch"
- end
-
- step 'I submit new branch form' do
- fill_in 'branch_name', with: 'deploy_keys'
- fill_in 'ref', with: 'master'
- click_button 'Create branch'
- end
-
- step 'I should see new branch created' do
- within '.tree-ref-holder' do
- page.should have_content 'deploy_keys'
- end
- end
-end
diff --git a/features/steps/project/browse_commits.rb b/features/steps/project/browse_commits.rb
deleted file mode 100644
index 37207aafeb..0000000000
--- a/features/steps/project/browse_commits.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-class ProjectBrowseCommits < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
- include RepoHelpers
-
- Then 'I see project commits' do
- commit = @project.repository.commit
- page.should have_content(@project.name)
- page.should have_content(commit.message[0..20])
- page.should have_content(commit.id.to_s[0..5])
- end
-
- Given 'I click atom feed link' do
- click_link "Feed"
- end
-
- Then 'I see commits atom feed' do
- commit = @project.repository.commit
- page.response_headers['Content-Type'].should have_content("application/atom+xml")
- page.body.should have_selector("title", text: "Recent commits to #{@project.name}")
- page.body.should have_selector("author email", text: commit.author_email)
- page.body.should have_selector("entry summary", text: commit.description[0..10])
- end
-
- Given 'I click on commit link' do
- visit project_commit_path(@project, sample_commit.id)
- end
-
- Then 'I see commit info' do
- page.should have_content sample_commit.message
- page.should have_content "Showing #{sample_commit.files_changed_count} changed files"
- end
-
- And 'I fill compare fields with refs' do
- fill_in "from", with: sample_commit.parent_id
- fill_in "to", with: sample_commit.id
- click_button "Compare"
- end
-
- Then 'I see compared refs' do
- page.should have_content "Compare View"
- page.should have_content "Commits (1)"
- page.should have_content "Showing 2 changed files"
- end
-
- Then 'I see breadcrumb links' do
- page.should have_selector('ul.breadcrumb')
- page.should have_selector('ul.breadcrumb a', count: 4)
- end
-
- Then 'I see commits stats' do
- page.should have_content 'Top 50 Committers'
- page.should have_content 'Committers'
- page.should have_content 'Total commits'
- page.should have_content 'Authors'
- end
-
- Given 'I visit big commit page' do
- Commit::DIFF_SAFE_FILES = 20
- visit project_commit_path(@project, sample_big_commit.id)
- end
-
- Then 'I see big commit warning' do
- page.should have_content sample_big_commit.message
- page.should have_content "Too many changes"
- Commit::DIFF_SAFE_FILES = 100
- end
-
- Given 'I visit a commit with an image that changed' do
- visit project_commit_path(@project, sample_image_commit.id)
- end
-
- Then 'The diff links to both the previous and current image' do
- links = page.all('.two-up span div a')
- links[0]['href'].should =~ %r{blob/#{sample_image_commit.old_blob_id}}
- links[1]['href'].should =~ %r{blob/#{sample_image_commit.new_blob_id}}
- end
-
- Given 'I click side-by-side diff button' do
- click_link "Side-by-side Diff"
- end
-
- Then 'I see side-by-side diff button' do
- page.should have_content "Side-by-side Diff"
- end
-
- Then 'I see inline diff button' do
- page.should have_content "Inline Diff"
- end
-end
diff --git a/features/steps/project/browse_files.rb b/features/steps/project/browse_files.rb
deleted file mode 100644
index 6fd0c2c2de..0000000000
--- a/features/steps/project/browse_files.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-class ProjectBrowseFiles < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
- include RepoHelpers
-
- step 'I should see files from repository' do
- page.should have_content "VERSION"
- page.should have_content ".gitignore"
- page.should have_content "LICENSE"
- end
-
- step 'I should see files from repository for "6d39438"' do
- current_path.should == project_tree_path(@project, "6d39438")
- page.should have_content ".gitignore"
- page.should have_content "LICENSE"
- end
-
- step 'I click on ".gitignore" file in repo' do
- click_link ".gitignore"
- end
-
- step 'I should see it content' do
- page.should have_content "*.rbc"
- end
-
- step 'I click link "raw"' do
- click_link "raw"
- end
-
- step 'I should see raw file content' do
- page.source.should == sample_blob.data
- end
-
- step 'I click button "edit"' do
- click_link 'edit'
- end
-
- step 'I can edit code' do
- page.execute_script('editor.setValue("GitlabFileEditor")')
- page.evaluate_script('editor.getValue()').should == "GitlabFileEditor"
- end
-
- step 'I edit code' do
- page.execute_script('editor.setValue("GitlabFileEditor")')
- end
-
- step 'I click link "Diff"' do
- click_link 'Diff'
- end
-
- step 'I see diff' do
- page.should have_css '.line_holder.new'
- end
-
- step 'I click on "new file" link in repo' do
- click_link 'new-file-link'
- end
-
- step 'I can see new file page' do
- page.should have_content "New file"
- page.should have_content "File name"
- page.should have_content "Commit message"
- end
-
- step 'I click on files directory' do
- click_link 'files'
- end
-
- step 'I click on history link' do
- click_link 'history'
- end
-
- step 'I see Browse dir link' do
- page.should have_link 'Browse Dir »'
- page.should_not have_link 'Browse Code »'
- end
-
- step 'I click on readme file' do
- click_link 'README.md'
- end
-
- step 'I see Browse file link' do
- page.should have_link 'Browse File »'
- page.should_not have_link 'Browse Code »'
- end
-
- step 'I see Browse code link' do
- page.should have_link 'Browse Code »'
- page.should_not have_link 'Browse File »'
- page.should_not have_link 'Browse Dir »'
- end
-end
diff --git a/features/steps/project/browse_tags.rb b/features/steps/project/browse_tags.rb
deleted file mode 100644
index 7c679911e0..0000000000
--- a/features/steps/project/browse_tags.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class ProjectBrowseTags < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- Then 'I should see "Shop" all tags list' do
- page.should have_content "Tags"
- page.should have_content "v1.0.0"
- end
-end
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
new file mode 100644
index 0000000000..07f7e5796a
--- /dev/null
+++ b/features/steps/project/commits/branches.rb
@@ -0,0 +1,85 @@
+class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'I click link "All"' do
+ click_link "All"
+ end
+
+ step 'I should see "Shop" all branches list' do
+ page.should have_content "Branches"
+ page.should have_content "master"
+ end
+
+ step 'I click link "Protected"' do
+ click_link "Protected"
+ end
+
+ step 'I should see "Shop" protected branches list' do
+ within ".protected-branches-list" do
+ page.should have_content "stable"
+ page.should_not have_content "master"
+ end
+ end
+
+ step 'project "Shop" has protected branches' do
+ project = Project.find_by(name: "Shop")
+ project.protected_branches.create(name: "stable")
+ end
+
+ step 'I click new branch link' do
+ click_link "New branch"
+ end
+
+ step 'I submit new branch form' do
+ fill_in 'branch_name', with: 'deploy_keys'
+ fill_in 'ref', with: 'master'
+ click_button 'Create branch'
+ end
+
+ step 'I submit new branch form with invalid name' do
+ fill_in 'branch_name', with: '1.0 stable'
+ fill_in 'ref', with: 'master'
+ click_button 'Create branch'
+ end
+
+ step 'I submit new branch form with invalid reference' do
+ fill_in 'branch_name', with: 'foo'
+ fill_in 'ref', with: 'foo'
+ click_button 'Create branch'
+ end
+
+ step 'I submit new branch form with branch that already exists' do
+ fill_in 'branch_name', with: 'master'
+ fill_in 'ref', with: 'master'
+ click_button 'Create branch'
+ end
+
+ step 'I should see new branch created' do
+ page.should have_content 'deploy_keys'
+ end
+
+ step 'I should see new an error that branch is invalid' do
+ page.should have_content 'Branch name invalid'
+ end
+
+ step 'I should see new an error that ref is invalid' do
+ page.should have_content 'Invalid reference name'
+ end
+
+ step 'I should see new an error that branch already exists' do
+ page.should have_content 'Branch already exists'
+ end
+
+ step "I click branch 'improve/awesome' delete link" do
+ within '.js-branch-improve\/awesome' do
+ find('.btn-remove').click
+ sleep 0.05
+ end
+ end
+
+ step "I should not see branch 'improve/awesome'" do
+ all(visible: true).should_not have_content 'improve/awesome'
+ end
+end
diff --git a/features/steps/project/comments_on_commits.rb b/features/steps/project/commits/comments.rb
similarity index 58%
rename from features/steps/project/comments_on_commits.rb
rename to features/steps/project/commits/comments.rb
index 56bb12a820..3d4d8ad636 100644
--- a/features/steps/project/comments_on_commits.rb
+++ b/features/steps/project/commits/comments.rb
@@ -1,4 +1,4 @@
-class CommentsOnCommits < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommitsComments < Spinach::FeatureSteps
include SharedAuthentication
include SharedNote
include SharedPaths
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
new file mode 100644
index 0000000000..57b727f837
--- /dev/null
+++ b/features/steps/project/commits/commits.rb
@@ -0,0 +1,103 @@
+class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+ include RepoHelpers
+
+ step 'I see project commits' do
+ commit = @project.repository.commit
+ page.should have_content(@project.name)
+ page.should have_content(commit.message[0..20])
+ page.should have_content(commit.short_id)
+ end
+
+ step 'I click atom feed link' do
+ click_link "Feed"
+ end
+
+ step 'I see commits atom feed' do
+ commit = @project.repository.commit
+ response_headers['Content-Type'].should have_content("application/atom+xml")
+ body.should have_selector("title", text: "Recent commits to #{@project.name}")
+ body.should have_selector("author email", text: commit.author_email)
+ body.should have_selector("entry summary", text: commit.description[0..10])
+ end
+
+ step 'I click on commit link' do
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ end
+
+ step 'I see commit info' do
+ page.should have_content sample_commit.message
+ page.should have_content "Showing #{sample_commit.files_changed_count} changed files"
+ end
+
+ step 'I fill compare fields with refs' do
+ fill_in "from", with: sample_commit.parent_id
+ fill_in "to", with: sample_commit.id
+ click_button "Compare"
+ end
+
+ step 'I unfold diff' do
+ @diff = first('.js-unfold')
+ @diff.click
+ sleep 2
+ end
+
+ step 'I should see additional file lines' do
+ within @diff.parent do
+ first('.new_line').text.should_not have_content "..."
+ end
+ end
+
+ step 'I see compared refs' do
+ page.should have_content "Compare View"
+ page.should have_content "Commits (1)"
+ page.should have_content "Showing 2 changed files"
+ end
+
+ step 'I see breadcrumb links' do
+ page.should have_selector('ul.breadcrumb')
+ page.should have_selector('ul.breadcrumb a', count: 4)
+ end
+
+ step 'I see commits stats' do
+ page.should have_content 'Top 50 Committers'
+ page.should have_content 'Committers'
+ page.should have_content 'Total commits'
+ page.should have_content 'Authors'
+ end
+
+ step 'I visit big commit page' do
+ Commit::DIFF_SAFE_FILES = 20
+ visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
+ end
+
+ step 'I see big commit warning' do
+ page.should have_content sample_big_commit.message
+ page.should have_content "Too many changes"
+ Commit::DIFF_SAFE_FILES = 100
+ end
+
+ step 'I visit a commit with an image that changed' do
+ visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id)
+ end
+
+ step 'The diff links to both the previous and current image' do
+ links = all('.two-up span div a')
+ links[0]['href'].should =~ %r{blob/#{sample_image_commit.old_blob_id}}
+ links[1]['href'].should =~ %r{blob/#{sample_image_commit.new_blob_id}}
+ end
+
+ step 'I click side-by-side diff button' do
+ click_link "Side-by-side"
+ end
+
+ step 'I see side-by-side diff button' do
+ page.should have_content "Side-by-side"
+ end
+
+ step 'I see inline diff button' do
+ page.should have_content "Inline"
+ end
+end
diff --git a/features/steps/project/comments_on_commit_diffs.rb b/features/steps/project/commits/diff_comments.rb
similarity index 58%
rename from features/steps/project/comments_on_commit_diffs.rb
rename to features/steps/project/commits/diff_comments.rb
index fc397a4fa9..b9d8cf2c5a 100644
--- a/features/steps/project/comments_on_commit_diffs.rb
+++ b/features/steps/project/commits/diff_comments.rb
@@ -1,4 +1,4 @@
-class CommentsOnCommitDiffs < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommitsDiffComments < Spinach::FeatureSteps
include SharedAuthentication
include SharedDiffNote
include SharedPaths
diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb
new file mode 100644
index 0000000000..3465fcbfd0
--- /dev/null
+++ b/features/steps/project/commits/tags.rb
@@ -0,0 +1,82 @@
+class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'I should see "Shop" all tags list' do
+ page.should have_content "Tags"
+ page.should have_content "v1.0.0"
+ end
+
+ step 'I click new tag link' do
+ click_link 'New tag'
+ end
+
+ step 'I submit new tag form' do
+ fill_in 'tag_name', with: 'v7.0'
+ fill_in 'ref', with: 'master'
+ click_button 'Create tag'
+ end
+
+ step 'I submit new tag form with invalid name' do
+ fill_in 'tag_name', with: 'v 1.0'
+ fill_in 'ref', with: 'master'
+ click_button 'Create tag'
+ end
+
+ step 'I submit new tag form with invalid reference' do
+ fill_in 'tag_name', with: 'foo'
+ fill_in 'ref', with: 'foo'
+ click_button 'Create tag'
+ end
+
+ step 'I submit new tag form with tag that already exists' do
+ fill_in 'tag_name', with: 'v1.0.0'
+ fill_in 'ref', with: 'master'
+ click_button 'Create tag'
+ end
+
+ step 'I should see new tag created' do
+ page.should have_content 'v7.0'
+ end
+
+ step 'I should see new an error that tag is invalid' do
+ page.should have_content 'Tag name invalid'
+ end
+
+ step 'I should see new an error that tag ref is invalid' do
+ page.should have_content 'Invalid reference name'
+ end
+
+ step 'I should see new an error that tag already exists' do
+ page.should have_content 'Tag already exists'
+ end
+
+ step "I delete tag 'v1.1.0'" do
+ within '.tags' do
+ first('.btn-remove').click
+ sleep 0.05
+ end
+ end
+
+ step "I should not see tag 'v1.1.0'" do
+ within '.tags' do
+ all(visible: true).should_not have_content 'v1.1.0'
+ end
+ end
+
+ step 'I delete all tags' do
+ within '.tags' do
+ all('.btn-remove').each do |remove|
+ remove.click
+ sleep 0.05
+ end
+ end
+ end
+
+ step 'I should see tags info message' do
+ within '.tags' do
+ page.should have_content 'Repository has no tags yet.'
+ end
+ end
+end
diff --git a/features/steps/project/browse_commits_user_lookup.rb b/features/steps/project/commits/user_lookup.rb
similarity index 77%
rename from features/steps/project/browse_commits_user_lookup.rb
rename to features/steps/project/commits/user_lookup.rb
index 198ea29f28..63ff84c82e 100644
--- a/features/steps/project/browse_commits_user_lookup.rb
+++ b/features/steps/project/commits/user_lookup.rb
@@ -1,14 +1,14 @@
-class ProjectBrowseCommitsUserLookup < Spinach::FeatureSteps
+class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
- Given 'I click on commit link' do
- visit project_commit_path(@project, sample_commit.id)
+ step 'I click on commit link' do
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
end
- Given 'I click on another commit link' do
- visit project_commit_path(@project, sample_commit.parent_id)
+ step 'I click on another commit link' do
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.parent_id)
end
step 'I have user with primary email' do
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index b42e5bd362..6b85cf74f5 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -1,42 +1,42 @@
-class CreateProject < Spinach::FeatureSteps
+class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
- And 'fill project form with valid data' do
- fill_in 'project_name', with: 'Empty'
+ step 'fill project form with valid data' do
+ fill_in 'project_path', with: 'Empty'
click_button "Create project"
end
- Then 'I should see project page' do
+ step 'I should see project page' do
page.should have_content "Empty"
- current_path.should == project_path(Project.last)
+ current_path.should == namespace_project_path(Project.last.namespace, Project.last)
end
- And 'I should see empty project instuctions' do
+ step 'I should see empty project instuctions' do
page.should have_content "git init"
page.should have_content "git remote"
page.should have_content Project.last.url_to_repo
end
- Then 'I see empty project instuctions' do
+ step 'I see empty project instuctions' do
page.should have_content "git init"
page.should have_content "git remote"
page.should have_content Project.last.url_to_repo
end
- And 'I click on HTTP' do
+ step 'I click on HTTP' do
click_button 'HTTP'
end
- Then 'Remote url should update to http link' do
+ step 'Remote url should update to http link' do
page.should have_content "git remote add origin #{Project.last.http_url_to_repo}"
end
- And 'If I click on SSH' do
+ step 'If I click on SSH' do
click_button 'SSH'
end
- Then 'Remote url should update to ssh link' do
+ step 'Remote url should update to ssh link' do
page.should have_content "git remote add origin #{Project.last.url_to_repo}"
end
end
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index 914da31322..50e14513a7 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -7,12 +7,24 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
create(:deploy_keys_project, project: @project)
end
- step 'I should see project deploy keys' do
+ step 'I should see project deploy key' do
within '.enabled-keys' do
page.should have_content deploy_key.title
end
end
+ step 'I should see other project deploy key' do
+ within '.available-keys' do
+ page.should have_content other_deploy_key.title
+ end
+ end
+
+ step 'I should see public deploy key' do
+ within '.available-keys' do
+ page.should have_content public_deploy_key.title
+ end
+ end
+
step 'I click \'New Deploy Key\'' do
click_link 'New Deploy Key'
end
@@ -24,7 +36,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should be on deploy keys page' do
- current_path.should == project_deploy_keys_path(@project)
+ current_path.should == namespace_project_deploy_keys_path(@project.namespace, @project)
end
step 'I should see newly created deploy key' do
@@ -39,6 +51,10 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
create(:deploy_keys_project, project: @second_project)
end
+ step 'public deploy key exists' do
+ create(:deploy_key, public: true)
+ end
+
step 'I click attach deploy key' do
within '.available-keys' do
click_link 'Enable'
@@ -50,4 +66,12 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
def deploy_key
@project.deploy_keys.last
end
+
+ def other_deploy_key
+ @second_project.deploy_keys.last
+ end
+
+ def public_deploy_key
+ DeployKey.are_public.last
+ end
end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 93ceaa0ebb..8e58597db2 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -1,4 +1,4 @@
-class ForkProject < Spinach::FeatureSteps
+class Spinach::Features::ProjectFork < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
@@ -25,4 +25,10 @@ class ForkProject < Spinach::FeatureSteps
step 'I should see a "Name has already been taken" warning' do
page.should have_content "Name has already been taken"
end
+
+ step 'I fork to my namespace' do
+ within '.fork-namespaces' do
+ click_link current_user.name
+ end
+ end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 6ec527df13..63ad90e124 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -1,4 +1,4 @@
-class ProjectForkedMergeRequests < Spinach::FeatureSteps
+class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedNote
@@ -23,7 +23,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I should see merge request "Merge Request On Forked Project"' do
@project.merge_requests.size.should >= 1
@merge_request = @project.merge_requests.last
- current_path.should == project_merge_request_path(@project, @merge_request)
+ current_path.should == namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
@merge_request.title.should == "Merge Request On Forked Project"
@merge_request.source_project.should == @forked_project
@merge_request.source_branch.should == "fix"
@@ -64,14 +64,14 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I see prefilled new Merge Request page for the forked project' do
- current_path.should == new_project_merge_request_path(@forked_project)
+ current_path.should == new_namespace_project_merge_request_path(@forked_project.namespace, @forked_project)
find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
find("#merge_request_target_project_id").value.should == @project.id.to_s
find("#merge_request_source_branch").value.should have_content "new_design"
find("#merge_request_target_branch").value.should have_content "master"
find("#merge_request_title").value.should == "New Design"
- verify_commit_link(".mr_target_commit",@project)
- verify_commit_link(".mr_source_commit",@forked_project)
+ verify_commit_link(".mr_target_commit", @project)
+ verify_commit_link(".mr_source_commit", @forked_project)
end
step 'I update the merge request title' do
@@ -86,7 +86,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
page.should have_content "An Edited Forked Merge Request"
@project.merge_requests.size.should >= 1
@merge_request = @project.merge_requests.last
- current_path.should == project_merge_request_path(@project, @merge_request)
+ current_path.should == namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
@merge_request.source_project.should == @forked_project
@merge_request.source_branch.should == "fix"
@merge_request.target_branch.should == "master"
@@ -106,7 +106,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
- current_path.should == edit_project_merge_request_path(@project, @merge_request)
+ current_path.should == edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
page.should have_content "Edit merge request ##{@merge_request.id}"
find("#merge_request_title").value.should == "Merge Request On Forked Project"
end
@@ -114,7 +114,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
select "Select branch", from: "merge_request_target_branch"
find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
- find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
+ find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s
find(:select, "merge_request_source_branch", {}).value.should == ""
find(:select, "merge_request_target_branch", {}).value.should == ""
click_button "Compare branches"
@@ -125,11 +125,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'the target repository should be the original repository' do
- page.should have_select("merge_request_target_project_id", selected: project.path_with_namespace)
- end
-
- def project
- @project ||= Project.find_by!(name: "Shop")
+ page.should have_select("merge_request_target_project_id", selected: @project.path_with_namespace)
end
# Verify a link is generated against the correct project
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index 89fe5fdead..a2807c340f 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -1,13 +1,23 @@
-class ProjectGraph < Spinach::FeatureSteps
+class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
- Then 'page should have graphs' do
+ step 'page should have graphs' do
page.should have_selector ".stat-graph"
end
When 'I visit project "Shop" graph page' do
project = Project.find_by(name: "Shop")
- visit project_graph_path(project, "master")
+ visit namespace_project_graph_path(project.namespace, project, "master")
+ end
+
+ step 'I visit project "Shop" commits graph page' do
+ project = Project.find_by(name: "Shop")
+ visit commits_namespace_project_graph_path(project.namespace, project, "master")
+ end
+
+ step 'page should have commits graphs' do
+ page.should have_content "Commit statistics for master"
+ page.should have_content "Commits per day of month"
end
end
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index 2bd383676e..4b13520259 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -1,6 +1,6 @@
require 'webmock'
-class ProjectHooks < Spinach::FeatureSteps
+class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
@@ -29,7 +29,7 @@ class ProjectHooks < Spinach::FeatureSteps
end
step 'I should see newly created hook' do
- page.current_path.should == project_hooks_path(current_project)
+ current_path.should == namespace_project_hooks_path(current_project.namespace, current_project)
page.should have_content(@url)
end
@@ -44,7 +44,7 @@ class ProjectHooks < Spinach::FeatureSteps
end
step 'hook should be triggered' do
- page.current_path.should == project_hooks_path(current_project)
+ current_path.should == namespace_project_hooks_path(current_project.namespace, current_project)
page.should have_selector '.flash-notice',
text: 'Hook successfully executed.'
end
diff --git a/features/steps/project/issue_tracker.rb b/features/steps/project/issue_tracker.rb
deleted file mode 100644
index c2fd4e15c9..0000000000
--- a/features/steps/project/issue_tracker.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-class ProjectIssueTracker < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- step 'project "Shop" has issues enabled' do
- @project = Project.find_by(name: "Shop")
- @project ||= create(:project, name: "Shop", namespace: @user.namespace)
- @project.issues_enabled = true
- end
-
- step 'change the issue tracker to "GitLab"' do
- select 'GitLab', from: 'project_issues_tracker'
- end
-
- step 'I the project should have "GitLab" as issue tracker' do
- find_field('project_issues_tracker').value.should == 'gitlab'
- end
-
- step 'change the issue tracker to "Redmine"' do
- select 'Redmine', from: 'project_issues_tracker'
- end
-
- step 'I the project should have "Redmine" as issue tracker' do
- find_field('project_issues_tracker').value.should == 'redmine'
- end
-
- And 'I save project' do
- click_button 'Save changes'
- end
-end
diff --git a/features/steps/project/issues.rb b/features/steps/project/issues.rb
deleted file mode 100644
index 557ea2fdca..0000000000
--- a/features/steps/project/issues.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-class ProjectIssues < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedNote
- include SharedPaths
- include SharedMarkdown
-
- Given 'I should see "Release 0.4" in issues' do
- page.should have_content "Release 0.4"
- end
-
- And 'I should not see "Release 0.3" in issues' do
- page.should_not have_content "Release 0.3"
- end
-
- And 'I should not see "Tweet control" in issues' do
- page.should_not have_content "Tweet control"
- end
-
- Given 'I click link "Closed"' do
- click_link "Closed"
- end
-
- Then 'I should see "Release 0.3" in issues' do
- page.should have_content "Release 0.3"
- end
-
- And 'I should not see "Release 0.4" in issues' do
- page.should_not have_content "Release 0.4"
- end
-
- Given 'I click link "All"' do
- click_link "All"
- end
-
- Given 'I click link "Release 0.4"' do
- click_link "Release 0.4"
- end
-
- Then 'I should see issue "Release 0.4"' do
- page.should have_content "Release 0.4"
- end
-
- Given 'I click link "New Issue"' do
- click_link "New Issue"
- end
-
- And 'I submit new issue "500 error on profile"' do
- fill_in "issue_title", with: "500 error on profile"
- click_button "Submit new issue"
- end
-
- step 'I submit new issue "500 error on profile" with label \'bug\'' do
- fill_in "issue_title", with: "500 error on profile"
- select 'bug', from: "Labels"
- click_button "Submit new issue"
- end
-
- Given 'I click link "500 error on profile"' do
- click_link "500 error on profile"
- end
-
- step 'I should see label \'bug\' with issue' do
- within '.issue-show-labels' do
- page.should have_content 'bug'
- end
- end
-
- Then 'I should see issue "500 error on profile"' do
- issue = Issue.find_by(title: "500 error on profile")
- page.should have_content issue.title
- page.should have_content issue.author_name
- page.should have_content issue.project.name
- end
-
- Given 'I fill in issue search with "Re"' do
- fill_in 'issue_search', with: "Re"
- end
-
- Given 'I fill in issue search with "Bu"' do
- fill_in 'issue_search', with: "Bu"
- end
-
- And 'I fill in issue search with ".3"' do
- fill_in 'issue_search', with: ".3"
- end
-
- And 'I fill in issue search with "Something"' do
- fill_in 'issue_search', with: "Something"
- end
-
- And 'I fill in issue search with ""' do
- fill_in 'issue_search', with: ""
- end
-
- Given 'project "Shop" has milestone "v2.2"' do
- project = Project.find_by(name: "Shop")
- milestone = create(:milestone, title: "v2.2", project: project)
-
- 3.times { create(:issue, project: project, milestone: milestone) }
- end
-
- And 'project "Shop" has milestone "v3.0"' do
- project = Project.find_by(name: "Shop")
- milestone = create(:milestone, title: "v3.0", project: project)
-
- 3.times { create(:issue, project: project, milestone: milestone) }
- end
-
- When 'I select milestone "v3.0"' do
- select "v3.0", from: "milestone_id"
- end
-
- Then 'I should see selected milestone with title "v3.0"' do
- issues_milestone_selector = "#issue_milestone_id_chzn > a"
- page.find(issues_milestone_selector).should have_content("v3.0")
- end
-
- When 'I select first assignee from "Shop" project' do
- project = Project.find_by(name: "Shop")
- first_assignee = project.users.first
- select first_assignee.name, from: "assignee_id"
- end
-
- Then 'I should see first assignee from "Shop" as selected assignee' do
- issues_assignee_selector = "#issue_assignee_id_chzn > a"
- project = Project.find_by(name: "Shop")
- assignee_name = project.users.first.name
- page.find(issues_assignee_selector).should have_content(assignee_name)
- end
-
- And 'project "Shop" have "Release 0.4" open issue' do
- project = Project.find_by(name: "Shop")
- create(:issue,
- title: "Release 0.4",
- project: project,
- author: project.users.first,
- description: "# Description header"
- )
- end
-
- And 'project "Shop" have "Tweet control" open issue' do
- project = Project.find_by(name: "Shop")
- create(:issue,
- title: "Tweet control",
- project: project,
- author: project.users.first)
- end
-
- And 'project "Shop" have "Release 0.3" closed issue' do
- project = Project.find_by(name: "Shop")
- create(:closed_issue,
- title: "Release 0.3",
- project: project,
- author: project.users.first)
- end
-
- Given 'empty project "Empty Project"' do
- create :empty_project, name: 'Empty Project', namespace: @user.namespace
- end
-
- When 'I visit empty project page' do
- project = Project.find_by(name: 'Empty Project')
- visit project_path(project)
- end
-
- And 'I see empty project details with ssh clone info' do
- project = Project.find_by(name: 'Empty Project')
- page.all(:css, '.git-empty .clone').each do |element|
- element.text.should include(project.url_to_repo)
- end
- end
-
- When "I visit empty project's issues page" do
- project = Project.find_by(name: 'Empty Project')
- visit project_issues_path(project)
- end
-
- step 'I leave a comment with code block' do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```"
- click_button "Add Comment"
- sleep 0.05
- end
- end
-
- step 'The code block should be unchanged' do
- page.should have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```")
- end
-end
diff --git a/features/steps/project/filter_labels.rb b/features/steps/project/issues/filter_labels.rb
similarity index 75%
rename from features/steps/project/filter_labels.rb
rename to features/steps/project/issues/filter_labels.rb
index 9b31a6d9da..5740bd1283 100644
--- a/features/steps/project/filter_labels.rb
+++ b/features/steps/project/issues/filter_labels.rb
@@ -1,25 +1,8 @@
-class ProjectFilterLabels < Spinach::FeatureSteps
+class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
-
- step 'I should see "bug" in labels filter' do
- within ".labels-filter" do
- page.should have_content "bug"
- end
- end
-
- step 'I should see "feature" in labels filter' do
- within ".labels-filter" do
- page.should have_content "feature"
- end
- end
-
- step 'I should see "enhancement" in labels filter' do
- within ".labels-filter" do
- page.should have_content "enhancement"
- end
- end
+ include Select2Helper
step 'I should see "Bugfix1" in issues list' do
within ".issues-list" do
@@ -46,9 +29,7 @@ class ProjectFilterLabels < Spinach::FeatureSteps
end
step 'I click link "bug"' do
- within ".labels-filter" do
- click_link "bug"
- end
+ select2('bug', from: "#label_name")
end
step 'I click link "feature"' do
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
new file mode 100644
index 0000000000..b8e282b202
--- /dev/null
+++ b/features/steps/project/issues/issues.rb
@@ -0,0 +1,276 @@
+class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedIssuable
+ include SharedProject
+ include SharedNote
+ include SharedPaths
+ include SharedMarkdown
+
+ step 'I should see "Release 0.4" in issues' do
+ page.should have_content "Release 0.4"
+ end
+
+ step 'I should not see "Release 0.3" in issues' do
+ page.should_not have_content "Release 0.3"
+ end
+
+ step 'I should not see "Tweet control" in issues' do
+ page.should_not have_content "Tweet control"
+ end
+
+ step 'I should see that I am subscribed' do
+ find(".subscribe-button span").text.should == "Unsubscribe"
+ end
+
+ step 'I should see that I am unsubscribed' do
+ sleep 0.2
+ find(".subscribe-button span").text.should == "Subscribe"
+ end
+
+ step 'I click link "Closed"' do
+ click_link "Closed"
+ end
+
+ step 'I click button "Unsubscribe"' do
+ click_on "Unsubscribe"
+ end
+
+ step 'I should see "Release 0.3" in issues' do
+ page.should have_content "Release 0.3"
+ end
+
+ step 'I should not see "Release 0.4" in issues' do
+ page.should_not have_content "Release 0.4"
+ end
+
+ step 'I click link "All"' do
+ click_link "All"
+ end
+
+ step 'I click link "Release 0.4"' do
+ click_link "Release 0.4"
+ end
+
+ step 'I should see issue "Release 0.4"' do
+ page.should have_content "Release 0.4"
+ end
+
+ step 'I click link "New Issue"' do
+ click_link "New Issue"
+ end
+
+ step 'I click "author" dropdown' do
+ first('.ajax-users-select').click
+ end
+
+ step 'I see current user as the first user' do
+ expect(page).to have_selector('.user-result', visible: true, count: 4)
+ users = page.all('.user-name')
+ users[0].text.should == 'Any'
+ users[1].text.should == 'Unassigned'
+ users[2].text.should == current_user.name
+ end
+
+ step 'I submit new issue "500 error on profile"' do
+ fill_in "issue_title", with: "500 error on profile"
+ click_button "Submit new issue"
+ end
+
+ step 'I submit new issue "500 error on profile" with label \'bug\'' do
+ fill_in "issue_title", with: "500 error on profile"
+ select 'bug', from: "Labels"
+ click_button "Submit new issue"
+ end
+
+ step 'I click link "500 error on profile"' do
+ click_link "500 error on profile"
+ end
+
+ step 'I should see label \'bug\' with issue' do
+ within '.issue-show-labels' do
+ page.should have_content 'bug'
+ end
+ end
+
+ step 'I should see issue "500 error on profile"' do
+ issue = Issue.find_by(title: "500 error on profile")
+ page.should have_content issue.title
+ page.should have_content issue.author_name
+ page.should have_content issue.project.name
+ end
+
+ step 'I fill in issue search with "Re"' do
+ filter_issue "Re"
+ end
+
+ step 'I fill in issue search with "Bu"' do
+ filter_issue "Bu"
+ end
+
+ step 'I fill in issue search with ".3"' do
+ filter_issue ".3"
+ end
+
+ step 'I fill in issue search with "Something"' do
+ filter_issue "Something"
+ end
+
+ step 'I fill in issue search with ""' do
+ filter_issue ""
+ end
+
+ step 'project "Shop" has milestone "v2.2"' do
+
+ milestone = create(:milestone, title: "v2.2", project: project)
+
+ 3.times { create(:issue, project: project, milestone: milestone) }
+ end
+
+ step 'project "Shop" has milestone "v3.0"' do
+
+ milestone = create(:milestone, title: "v3.0", project: project)
+
+ 3.times { create(:issue, project: project, milestone: milestone) }
+ end
+
+ When 'I select milestone "v3.0"' do
+ select "v3.0", from: "milestone_id"
+ end
+
+ step 'I should see selected milestone with title "v3.0"' do
+ issues_milestone_selector = "#issue_milestone_id_chzn > a"
+ find(issues_milestone_selector).should have_content("v3.0")
+ end
+
+ When 'I select first assignee from "Shop" project' do
+
+ first_assignee = project.users.first
+ select first_assignee.name, from: "assignee_id"
+ end
+
+ step 'I should see first assignee from "Shop" as selected assignee' do
+ issues_assignee_selector = "#issue_assignee_id_chzn > a"
+
+ assignee_name = project.users.first.name
+ find(issues_assignee_selector).should have_content(assignee_name)
+ end
+
+ step 'project "Shop" have "Release 0.4" open issue' do
+
+ create(:issue,
+ title: "Release 0.4",
+ project: project,
+ author: project.users.first,
+ description: "# Description header"
+ )
+ end
+
+ step 'project "Shop" have "Tweet control" open issue' do
+ create(:issue,
+ title: "Tweet control",
+ project: project,
+ author: project.users.first)
+ end
+
+ step 'project "Shop" have "Release 0.3" closed issue' do
+ create(:closed_issue,
+ title: "Release 0.3",
+ project: project,
+ author: project.users.first)
+ end
+
+ step 'project "Shop" has "Tasks-open" open issue with task markdown' do
+ create_taskable(:issue, 'Tasks-open')
+ end
+
+ step 'project "Shop" has "Tasks-closed" closed issue with task markdown' do
+ create_taskable(:closed_issue, 'Tasks-closed')
+ end
+
+ step 'empty project "Empty Project"' do
+ create :empty_project, name: 'Empty Project', namespace: @user.namespace
+ end
+
+ When 'I visit empty project page' do
+ project = Project.find_by(name: 'Empty Project')
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ step 'I see empty project details with ssh clone info' do
+ project = Project.find_by(name: 'Empty Project')
+ all(:css, '.git-empty .clone').each do |element|
+ element.text.should include(project.url_to_repo)
+ end
+ end
+
+ When "I visit empty project's issues page" do
+ project = Project.find_by(name: 'Empty Project')
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ step 'I leave a comment with code block' do
+ within(".js-main-target-form") do
+ fill_in "note[note]", with: "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```"
+ click_button "Add Comment"
+ sleep 0.05
+ end
+ end
+
+ step 'I should see an error alert section within the comment form' do
+ within(".js-main-target-form") do
+ find(".error-alert")
+ end
+ end
+
+ step 'The code block should be unchanged' do
+ page.should have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```")
+ end
+
+ step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do
+ issue = create(:issue, title: 'Bugfix1', description: 'Description for issue1', project: project)
+ end
+
+ step 'project \'Shop\' has issue \'Feature1\' with description: \'Feature submitted for issue1\'' do
+ issue = create(:issue, title: 'Feature1', description: 'Feature submitted for issue1', project: project)
+ end
+
+ step 'I fill in issue search with \'Description for issue1\'' do
+ filter_issue 'Description for issue'
+ end
+
+ step 'I fill in issue search with \'issue1\'' do
+ filter_issue 'issue1'
+ end
+
+ step 'I fill in issue search with \'Rock and roll\'' do
+ filter_issue 'Description for issue'
+ end
+
+ step 'I should see \'Bugfix1\' in issues' do
+ page.should have_content 'Bugfix1'
+ end
+
+ step 'I should see \'Feature1\' in issues' do
+ page.should have_content 'Feature1'
+ end
+
+ step 'I should not see \'Bugfix1\' in issues' do
+ page.should_not have_content 'Bugfix1'
+ end
+
+ step 'issue \'Release 0.4\' has label \'bug\'' do
+ label = project.labels.create!(name: 'bug', color: '#990000')
+ issue = Issue.find_by!(title: 'Release 0.4')
+ issue.labels << label
+ end
+
+ step 'I click label \'bug\'' do
+ within ".issues-list" do
+ click_link 'bug'
+ end
+ end
+
+ def filter_issue(text)
+ fill_in 'issue_search', with: text
+ end
+end
diff --git a/features/steps/project/labels.rb b/features/steps/project/issues/labels.rb
similarity index 74%
rename from features/steps/project/labels.rb
rename to features/steps/project/issues/labels.rb
index 8320405e09..6ce34c500c 100644
--- a/features/steps/project/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -1,22 +1,10 @@
-class ProjectLabels < Spinach::FeatureSteps
+class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
- step 'I should see label "bug"' do
- within ".manage-labels-list" do
- page.should have_content "bug"
- end
- end
-
- step 'I should see label "feature"' do
- within ".manage-labels-list" do
- page.should have_content "feature"
- end
- end
-
step 'I visit \'bug\' label edit page' do
- visit edit_project_label_path(project, bug_label)
+ visit edit_namespace_project_label_path(project.namespace, project, bug_label)
end
step 'I remove label \'bug\'' do
@@ -25,6 +13,22 @@ class ProjectLabels < Spinach::FeatureSteps
end
end
+ step 'I delete all labels' do
+ within '.labels' do
+ all('.btn-remove').each do |remove|
+ remove.click
+ sleep 0.05
+ end
+ end
+ end
+
+ step 'I should see labels help message' do
+ within '.labels' do
+ page.should have_content 'Create first label or generate default set of '\
+ 'labels'
+ end
+ end
+
step 'I submit new label \'support\'' do
fill_in 'Title', with: 'support'
fill_in 'Background Color', with: '#F95610'
@@ -55,6 +59,12 @@ class ProjectLabels < Spinach::FeatureSteps
end
end
+ step 'I should see label \'feature\'' do
+ within '.manage-labels-list' do
+ page.should have_content 'feature'
+ end
+ end
+
step 'I should see label \'bug\'' do
within '.manage-labels-list' do
page.should have_content 'bug'
diff --git a/features/steps/project/milestones.rb b/features/steps/project/issues/milestones.rb
similarity index 72%
rename from features/steps/project/milestones.rb
rename to features/steps/project/issues/milestones.rb
index 5562b523d1..cce87a6d98 100644
--- a/features/steps/project/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -1,37 +1,37 @@
-class ProjectMilestones < Spinach::FeatureSteps
+class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
include SharedMarkdown
- Then 'I should see milestone "v2.2"' do
+ step 'I should see milestone "v2.2"' do
milestone = @project.milestones.find_by(title: "v2.2")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
- page.should have_content("Browse Issues")
+ page.should have_content("Issues")
end
- Given 'I click link "v2.2"' do
+ step 'I click link "v2.2"' do
click_link "v2.2"
end
- Given 'I click link "New Milestone"' do
+ step 'I click link "New Milestone"' do
click_link "New Milestone"
end
- And 'I submit new milestone "v2.3"' do
+ step 'I submit new milestone "v2.3"' do
fill_in "milestone_title", with: "v2.3"
click_button "Create milestone"
end
- Then 'I should see milestone "v2.3"' do
+ step 'I should see milestone "v2.3"' do
milestone = @project.milestones.find_by(title: "v2.3")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
- page.should have_content("Browse Issues")
+ page.should have_content("Issues")
end
- And 'project "Shop" has milestone "v2.2"' do
+ step 'project "Shop" has milestone "v2.2"' do
project = Project.find_by(name: "Shop")
milestone = create(:milestone,
title: "v2.2",
@@ -41,7 +41,7 @@ class ProjectMilestones < Spinach::FeatureSteps
3.times { create(:issue, project: project, milestone: milestone) }
end
- Given 'the milestone has open and closed issues' do
+ step 'the milestone has open and closed issues' do
project = Project.find_by(name: "Shop")
milestone = project.milestones.find_by(title: 'v2.2')
@@ -53,7 +53,7 @@ class ProjectMilestones < Spinach::FeatureSteps
click_link 'All Issues'
end
- Then "I should see 3 issues" do
+ step 'I should see 3 issues' do
page.should have_selector('#tab-issues li.issue-row', count: 4)
end
end
diff --git a/features/steps/project/markdown_render.rb b/features/steps/project/markdown_render.rb
deleted file mode 100644
index 1885649891..0000000000
--- a/features/steps/project/markdown_render.rb
+++ /dev/null
@@ -1,277 +0,0 @@
-# If you need to modify the existing seed repository for your tests,
-# it is recommended that you make the changes on the `markdown` branch of the seed project repository,
-# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info.
-class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedMarkdown
-
- And 'I own project "Delta"' do
- @project = Project.find_by(name: "Delta")
- @project ||= create(:project, name: "Delta", namespace: @user.namespace)
- @project.team << [@user, :master]
- end
-
- Then 'I should see files from repository in markdown' do
- current_path.should == project_tree_path(@project, "markdown")
- page.should have_content "README.md"
- page.should have_content "CHANGELOG"
- end
-
- And 'I should see rendered README which contains correct links' do
- page.should have_content "Welcome to GitLab GitLab is a free project and repository management application"
- page.should have_link "GitLab API doc"
- page.should have_link "GitLab API website"
- page.should have_link "Rake tasks"
- page.should have_link "backup and restore procedure"
- page.should have_link "GitLab API doc directory"
- page.should have_link "Maintenance"
- end
-
- And 'I click on Gitlab API in README' do
- click_link "GitLab API doc"
- end
-
- Then 'I should see correct document rendered' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
- page.should have_content "All API requests require authentication"
- end
-
- And 'I click on Rake tasks in README' do
- click_link "Rake tasks"
- end
-
- Then 'I should see correct directory rendered' do
- current_path.should == project_tree_path(@project, "markdown/doc/raketasks")
- page.should have_content "backup_restore.md"
- page.should have_content "maintenance.md"
- end
-
- And 'I click on GitLab API doc directory in README' do
- click_link "GitLab API doc directory"
- end
-
- Then 'I should see correct doc/api directory rendered' do
- current_path.should == project_tree_path(@project, "markdown/doc/api")
- page.should have_content "README.md"
- page.should have_content "users.md"
- end
-
- And 'I click on Maintenance in README' do
- click_link "Maintenance"
- end
-
- Then 'I should see correct maintenance file rendered' do
- current_path.should == project_blob_path(@project, "markdown/doc/raketasks/maintenance.md")
- page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
- end
-
- And 'I click on link "empty" in the README' do
- within('.readme-holder') do
- click_link "empty"
- end
- end
-
- And 'I click on link "id" in the README' do
- within('.readme-holder') do
- click_link "#id"
- end
- end
-
- And 'I navigate to the doc/api/README' do
- click_link "doc"
- click_link "api"
- click_link "README.md"
- end
-
- And 'I see correct file rendered' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
- page.should have_content "Contents"
- page.should have_link "Users"
- page.should have_link "Rake tasks"
- end
-
- And 'I click on users in doc/api/README' do
- click_link "Users"
- end
-
- Then 'I should see the correct document file' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/users.md")
- page.should have_content "Get a list of users."
- end
-
- And 'I click on raketasks in doc/api/README' do
- click_link "Rake tasks"
- end
-
- # Markdown branch
-
- When 'I visit markdown branch' do
- visit project_tree_path(@project, "markdown")
- end
-
- When 'I visit markdown branch "README.md" blob' do
- visit project_blob_path(@project, "markdown/README.md")
- end
-
- When 'I visit markdown branch "d" tree' do
- visit project_tree_path(@project, "markdown/d")
- end
-
- When 'I visit markdown branch "d/README.md" blob' do
- visit project_blob_path(@project, "markdown/d/README.md")
- end
-
- Then 'I should see files from repository in markdown branch' do
- current_path.should == project_tree_path(@project, "markdown")
- page.should have_content "README.md"
- page.should have_content "CHANGELOG"
- end
-
- And 'I see correct file rendered in markdown branch' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
- page.should have_content "Contents"
- page.should have_link "Users"
- page.should have_link "Rake tasks"
- end
-
- Then 'I should see correct document rendered for markdown branch' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
- page.should have_content "All API requests require authentication"
- end
-
- Then 'I should see correct directory rendered for markdown branch' do
- current_path.should == project_tree_path(@project, "markdown/doc/raketasks")
- page.should have_content "backup_restore.md"
- page.should have_content "maintenance.md"
- end
-
- Then 'I should see the users document file in markdown branch' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/users.md")
- page.should have_content "Get a list of users."
- end
-
- # Expected link contents
-
- Then 'The link with text "empty" should have url "tree/markdown"' do
- find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown")
- end
-
- Then 'The link with text "empty" should have url "blob/markdown/README.md"' do
- find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md")
- end
-
- Then 'The link with text "empty" should have url "tree/markdown/d"' do
- find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d")
- end
-
- Then 'The link with text "empty" should have url "blob/markdown/d/README.md"' do
- find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md")
- end
-
- Then 'The link with text "ID" should have url "tree/markdownID"' do
- find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
- end
-
- Then 'The link with text "/ID" should have url "tree/markdownID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
- end
-
- Then 'The link with text "README.mdID" should have url "blob/markdown/README.mdID"' do
- find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
- end
-
- Then 'The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"' do
- find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
- end
-
- Then 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
- find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
- end
-
- Then 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
- end
-
- # Wiki
-
- Given 'I go to wiki page' do
- click_link "Wiki"
- current_path.should == project_wiki_path(@project, "home")
- end
-
- And 'I add various links to the wiki page' do
- fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n"
- fill_in "wiki[message]", with: "Adding links to wiki"
- click_button "Create page"
- end
-
- Then 'Wiki page should have added links' do
- current_path.should == project_wiki_path(@project, "home")
- page.should have_content "test GitLab API doc Rake tasks"
- end
-
- step 'I add a header to the wiki page' do
- fill_in "wiki[content]", with: "# Wiki header\n"
- fill_in "wiki[message]", with: "Add header to wiki"
- click_button "Create page"
- end
-
- step 'Wiki header should have correct id and link' do
- header_should_have_correct_id_and_link(1, 'Wiki header', 'wiki-header')
- end
-
- And 'I click on test link' do
- click_link "test"
- end
-
- Then 'I see new wiki page named test' do
- current_path.should == project_wiki_path(@project, "test")
- page.should have_content "Editing"
- end
-
- When 'I go back to wiki page home' do
- visit project_wiki_path(@project, "home")
- current_path.should == project_wiki_path(@project, "home")
- end
-
- And 'I click on GitLab API doc link' do
- click_link "GitLab API"
- end
-
- Then 'I see Gitlab API document' do
- current_path.should == project_wiki_path(@project, "api")
- page.should have_content "Editing"
- end
-
- And 'I click on Rake tasks link' do
- click_link "Rake tasks"
- end
-
- Then 'I see Rake tasks directory' do
- current_path.should == project_wiki_path(@project, "raketasks")
- page.should have_content "Editing"
- end
-
- Given 'I go directory which contains README file' do
- visit project_tree_path(@project, "markdown/doc/api")
- current_path.should == project_tree_path(@project, "markdown/doc/api")
- end
-
- And 'I click on a relative link in README' do
- click_link "Users"
- end
-
- Then 'I should see the correct markdown' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/users.md")
- page.should have_content "List users"
- end
-
- step 'Header "Application details" should have correct id and link' do
- header_should_have_correct_id_and_link(2, 'Application details', 'application-details')
- end
-
- step 'Header "GitLab API" should have correct id and link' do
- header_should_have_correct_id_and_link(1, 'GitLab API', 'gitlab-api')
- end
-end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 05d3e5067c..bb1f9f129c 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -1,9 +1,11 @@
-class ProjectMergeRequests < Spinach::FeatureSteps
+class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
include SharedAuthentication
+ include SharedIssuable
include SharedProject
include SharedNote
include SharedPaths
include SharedMarkdown
+ include SharedDiffNote
step 'I click link "New Merge Request"' do
click_link "New Merge Request"
@@ -54,10 +56,20 @@ class ProjectMergeRequests < Spinach::FeatureSteps
page.should_not have_content "Bug NS-04"
end
+ step 'I should see that I am subscribed' do
+ find(".subscribe-button span").text.should == "Unsubscribe"
+ end
+
+ step 'I should see that I am unsubscribed' do
+ find(".subscribe-button span").should have_content("Subscribe")
+ end
+
+ step 'I click button "Unsubscribe"' do
+ click_on "Unsubscribe"
+ end
+
step 'I click link "Close"' do
- within '.page-title' do
- click_link "Close"
- end
+ first(:css, '.close-mr-link').click
end
step 'I submit new merge request "Wiki Feature"' do
@@ -96,17 +108,39 @@ class ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first)
end
+ step 'project "Shop" has "MR-task-open" open MR with task markdown' do
+ create_taskable(:merge_request, 'MR-task-open')
+ end
+
step 'I switch to the diff tab' do
- visit diffs_project_merge_request_path(project, merge_request)
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ step 'I click on the Changes tab via Javascript' do
+ find('.diffs-tab').click
+ sleep 2
+ end
+
+ step 'I should see the proper Inline and Side-by-side links' do
+ buttons = all('#commit-diff-viewtype')
+ expect(buttons.count).to eq(2)
+
+ buttons.each do |b|
+ expect(b['href']).should_not have_content('json')
+ end
end
step 'I switch to the merge request\'s comments tab' do
- visit project_merge_request_path(project, merge_request)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
step 'I click on the commit in the merge request' do
- within '.mr-commits' do
- click_link sample_commit.id[0..8]
+ within '.merge-request-tabs' do
+ click_link 'Commits'
+ end
+
+ within '.commits' do
+ click_link Commit.truncate_sha(sample_commit.id)
end
end
@@ -148,12 +182,12 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
step 'merge request is mergeable' do
- page.should have_content 'You can accept this request automatically'
+ page.should have_button 'Accept Merge Request'
end
step 'I modify merge commit message' do
find('.modify-merge-commit-link').click
- fill_in 'merge_commit_message', with: "wow such merge"
+ fill_in 'commit_message', with: 'wow such merge'
end
step 'merge request "Bug NS-05" is mergeable' do
@@ -165,7 +199,9 @@ class ProjectMergeRequests < Spinach::FeatureSteps
merge!: true,
)
- click_button "Accept Merge Request"
+ within '.can_be_merged' do
+ click_button "Accept Merge Request"
+ end
end
step 'I should see merged request' do
@@ -175,26 +211,24 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click link "Reopen"' do
- within '.page-title' do
- click_link "Reopen"
- end
+ first(:css, '.reopen-mr-link').click
end
step 'I should see reopened merge request "Bug NS-04"' do
- within '.state-label' do
+ within '.issue-box' do
page.should have_content "Open"
end
end
step 'I click link "Hide inline discussion" of the second file' do
within '.files [id^=diff]:nth-child(2)' do
- click_link "Diff comments"
+ find('.js-toggle-diff-comments').click
end
end
step 'I click link "Show inline discussion" of the second file' do
within '.files [id^=diff]:nth-child(2)' do
- click_link "Diff comments"
+ find('.js-toggle-diff-comments').click
end
end
@@ -205,11 +239,23 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see a comment like "Line is wrong" in the second file' do
- within '.files [id^=diff]:nth-child(2) .note-text' do
+ within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
page.should have_visible_content "Line is wrong"
end
end
+ step 'I should not see a comment like "Line is wrong here" in the second file' do
+ within '.files [id^=diff]:nth-child(2)' do
+ page.should_not have_visible_content "Line is wrong here"
+ end
+ end
+
+ step 'I should see a comment like "Line is wrong here" in the second file' do
+ within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
+ page.should have_visible_content "Line is wrong here"
+ end
+ end
+
step 'I leave a comment like "Line is correct" on line 12 of the first file' do
init_diff_note_first_file
@@ -218,7 +264,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps
click_button "Add Comment"
end
- within ".files [id^=diff]:nth-child(1) .note-text" do
+ within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do
page.should have_content "Line is correct"
end
end
@@ -227,17 +273,13 @@ class ProjectMergeRequests < Spinach::FeatureSteps
init_diff_note_second_file
within(".js-discussion-note-form") do
- fill_in "note_note", with: "Line is wrong"
+ fill_in "note_note", with: "Line is wrong on here"
click_button "Add Comment"
end
-
- within ".files [id^=diff]:nth-child(2) .note-text" do
- page.should have_content "Line is wrong"
- end
end
step 'I should still see a comment like "Line is correct" in the first file' do
- within '.files [id^=diff]:nth-child(1) .note-text' do
+ within '.files [id^=diff]:nth-child(1) .note-body > .note-text' do
page.should have_visible_content "Line is correct"
end
end
@@ -250,8 +292,18 @@ class ProjectMergeRequests < Spinach::FeatureSteps
expect(first('.text-file')).to have_content('.bundle')
end
- def project
- @project ||= Project.find_by!(name: "Shop")
+ step 'I click Side-by-side Diff tab' do
+ find('a', text: 'Side-by-side').trigger('click')
+ end
+
+ step 'I should see comments on the side-by-side diff page' do
+ within '.files [id^=diff]:nth-child(1) .parallel .note-body > .note-text' do
+ page.should have_visible_content "Line is correct"
+ end
+ end
+
+ step 'I fill in merge request search with "Fe"' do
+ fill_in 'issue_search', with: "Fe"
end
def merge_request
@@ -282,8 +334,4 @@ class ProjectMergeRequests < Spinach::FeatureSteps
def have_visible_content (text)
have_css("*", text: text, visible: true)
end
-
- def click_diff_line(code)
- find("a[data-line-code='#{code}']").click
- end
end
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 9f5da28891..a15688ace6 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -1,9 +1,9 @@
-class ProjectNetworkGraph < Spinach::FeatureSteps
+class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
- Then 'page should have network graph' do
+ step 'page should have network graph' do
page.should have_selector ".network-graph"
end
@@ -12,30 +12,30 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
Network::Graph.stub(max_count: 10)
project = Project.find_by(name: "Shop")
- visit project_network_path(project, "master")
+ visit namespace_project_network_path(project.namespace, project, "master")
end
- And 'page should select "master" in select box' do
+ step 'page should select "master" in select box' do
page.should have_selector '.select2-chosen', text: "master"
end
- And 'page should select "v1.0.0" in select box' do
+ step 'page should select "v1.0.0" in select box' do
page.should have_selector '.select2-chosen', text: "v1.0.0"
end
- And 'page should have "master" on graph' do
+ step 'page should have "master" on graph' do
within '.network-graph' do
page.should have_content 'master'
end
end
When 'I switch ref to "feature"' do
- page.select 'feature', from: 'ref'
+ select 'feature', from: 'ref'
sleep 2
end
When 'I switch ref to "v1.0.0"' do
- page.select 'v1.0.0', from: 'ref'
+ select 'v1.0.0', from: 'ref'
sleep 2
end
@@ -44,27 +44,27 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
sleep 2
end
- Then 'page should have content not containing "v1.0.0"' do
+ step 'page should have content not containing "v1.0.0"' do
within '.network-graph' do
page.should have_content 'Change some files'
end
end
- Then 'page should not have content not containing "v1.0.0"' do
+ step 'page should not have content not containing "v1.0.0"' do
within '.network-graph' do
page.should_not have_content 'Change some files'
end
end
- And 'page should select "feature" in select box' do
+ step 'page should select "feature" in select box' do
page.should have_selector '.select2-chosen', text: "feature"
end
- And 'page should select "v1.0.0" in select box' do
+ step 'page should select "v1.0.0" in select box' do
page.should have_selector '.select2-chosen', text: "v1.0.0"
end
- And 'page should have "feature" on graph' do
+ step 'page should have "feature" on graph' do
within '.network-graph' do
page.should have_content 'feature'
end
@@ -78,7 +78,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
sleep 2
end
- And 'page should have "v1.0.0" on graph' do
+ step 'page should have "v1.0.0" on graph' do
within '.network-graph' do
page.should have_content 'v1.0.0'
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 2ffa1a6297..d39c8e7d2d 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -1,10 +1,10 @@
-class ProjectFeature < Spinach::FeatureSteps
+class Spinach::Features::Project < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
step 'change project settings' do
- fill_in 'project_name', with: 'NewName'
+ fill_in 'project_name_edit', with: 'NewName'
uncheck 'project_issues_enabled'
end
@@ -17,23 +17,58 @@ class ProjectFeature < Spinach::FeatureSteps
end
step 'change project path settings' do
- fill_in "project_path", with: "new-path"
- click_button "Rename"
+ fill_in 'project_path', with: 'new-path'
+ click_button 'Rename'
end
step 'I should see project with new path settings' do
- project.path.should == "new-path"
+ project.path.should == 'new-path'
end
- step 'I should see project "Shop" README link' do
- within '.project-side' do
- page.should have_content "README.md"
- end
+ step 'I change the project avatar' do
+ attach_file(
+ :project_avatar,
+ File.join(Rails.root, 'public', 'gitlab_logo.png')
+ )
+ click_button 'Save changes'
+ @project.reload
+ end
+
+ step 'I should see new project avatar' do
+ @project.avatar.should be_instance_of AvatarUploader
+ url = @project.avatar.url
+ url.should == "/uploads/project/avatar/#{ @project.id }/gitlab_logo.png"
+ end
+
+ step 'I should see the "Remove avatar" button' do
+ page.should have_link('Remove avatar')
+ end
+
+ step 'I have an project avatar' do
+ attach_file(
+ :project_avatar,
+ File.join(Rails.root, 'public', 'gitlab_logo.png')
+ )
+ click_button 'Save changes'
+ @project.reload
+ end
+
+ step 'I remove my project avatar' do
+ click_link 'Remove avatar'
+ @project.reload
+ end
+
+ step 'I should see the default project avatar' do
+ @project.avatar?.should be_false
+ end
+
+ step 'I should not see the "Remove avatar" button' do
+ page.should_not have_link('Remove avatar')
end
step 'I should see project "Shop" version' do
within '.project-side' do
- page.should have_content "Version: 6.7.0.pre"
+ page.should have_content 'Version: 6.7.0.pre'
end
end
@@ -45,4 +80,18 @@ class ProjectFeature < Spinach::FeatureSteps
step 'I should see project default branch changed' do
find(:css, 'select#project_default_branch').value.should == 'fix'
end
+
+ step 'I select project "Forum" README tab' do
+ click_link 'Readme'
+ end
+
+ step 'I should see project "Forum" README' do
+ page.should have_link 'README.md'
+ page.should have_content 'Sample repo for testing gitlab features'
+ end
+
+ step 'I should see project "Shop" README' do
+ page.should have_link 'README.md'
+ page.should have_content 'testme'
+ end
end
diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb
new file mode 100644
index 0000000000..a10e7bf78e
--- /dev/null
+++ b/features/steps/project/project_shortcuts.rb
@@ -0,0 +1,36 @@
+class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+ include SharedProjectTab
+
+ step 'I press "g" and "f"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('f')
+ end
+
+ step 'I press "g" and "c"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('c')
+ end
+
+ step 'I press "g" and "n"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('n')
+ end
+
+ step 'I press "g" and "g"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('g')
+ end
+
+ step 'I press "g" and "s"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('s')
+ end
+
+ step 'I press "g" and "w"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('w')
+ end
+end
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index 7e01735af9..57c6e39c80 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -13,30 +13,28 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I visit project "Community" page' do
project = Project.find_by(name: 'Community')
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I should see project "Community" home page' do
- Gitlab.config.gitlab.stub(:host).and_return("www.example.com")
- within '.project-home-title' do
+ Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com")
+ within '.navbar-gitlab .title' do
page.should have_content 'Community'
end
end
step 'I visit project "Enterprise" page' do
project = Project.find_by(name: 'Enterprise')
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I visit project "CommunityDoesNotExist" page' do
project = Project.find_by(name: 'Community')
- visit project_path(project) + 'DoesNotExist'
+ visit namespace_project_path(project.namespace, project) + 'DoesNotExist'
end
step 'I click on "Sign In"' do
- within '.pull-right' do
- click_link "Sign in"
- end
+ first(:link, "Sign in").click
end
step 'Authenticate' do
@@ -50,8 +48,8 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I should be redirected to "Community" page' do
project = Project.find_by(name: 'Community')
- page.current_path.should == "/#{project.path_with_namespace}"
- page.status_code.should == 200
+ current_path.should == "/#{project.path_with_namespace}"
+ status_code.should == 200
end
step 'I get redirected to signin page where I sign in' do
@@ -65,7 +63,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I should be redirected to "Enterprise" page' do
project = Project.find_by(name: 'Enterprise')
- page.current_path.should == "/#{project.path_with_namespace}"
- page.status_code.should == 200
+ current_path.should == "/#{project.path_with_namespace}"
+ status_code.should == 200
end
end
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 0594a08a5e..4b3d79324a 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -1,18 +1,23 @@
-class ProjectServices < Spinach::FeatureSteps
+class Spinach::Features::ProjectServices < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
step 'I visit project "Shop" services page' do
- visit project_services_path(@project)
+ visit namespace_project_services_path(@project.namespace, @project)
end
step 'I should see list of available services' do
page.should have_content 'Project services'
page.should have_content 'Campfire'
- page.should have_content 'Hipchat'
+ page.should have_content 'HipChat'
page.should have_content 'GitLab CI'
page.should have_content 'Assembla'
+ page.should have_content 'Pushover'
+ page.should have_content 'Atlassian Bamboo'
+ page.should have_content 'JetBrains TeamCity'
+ page.should have_content 'Asana'
+ page.should have_content 'Irker (IRC gateway)'
end
step 'I click gitlab-ci service link' do
@@ -31,7 +36,7 @@ class ProjectServices < Spinach::FeatureSteps
end
step 'I click hipchat service link' do
- click_link 'Hipchat'
+ click_link 'HipChat'
end
step 'I fill hipchat settings' do
@@ -45,6 +50,17 @@ class ProjectServices < Spinach::FeatureSteps
find_field('Room').value.should == 'gitlab'
end
+ step 'I fill hipchat settings with custom server' do
+ check 'Active'
+ fill_in 'Room', with: 'gitlab_custom'
+ fill_in 'Token', with: 'secretCustom'
+ fill_in 'Server', with: 'https://chat.example.com'
+ click_button 'Save'
+ end
+
+ step 'I should see hipchat service settings with custom server saved' do
+ find_field('Server').value.should == 'https://chat.example.com'
+ end
step 'I click pivotaltracker service link' do
click_link 'PivotalTracker'
@@ -88,6 +104,22 @@ class ProjectServices < Spinach::FeatureSteps
find_field('Token').value.should == 'verySecret'
end
+ step 'I click Asana service link' do
+ click_link 'Asana'
+ end
+
+ step 'I fill Asana settings' do
+ check 'Active'
+ fill_in 'Api key', with: 'verySecret'
+ fill_in 'Restrict to branch', with: 'master'
+ click_button 'Save'
+ end
+
+ step 'I should see Asana service settings saved' do
+ find_field('Api key').value.should == 'verySecret'
+ find_field('Restrict to branch').value.should == 'master'
+ end
+
step 'I click email on push service link' do
click_link 'Emails on push'
end
@@ -101,21 +133,93 @@ class ProjectServices < Spinach::FeatureSteps
find_field('Recipients').value.should == 'qa@company.name'
end
+ step 'I click Irker service link' do
+ click_link 'Irker (IRC gateway)'
+ end
+
+ step 'I fill Irker settings' do
+ check 'Active'
+ fill_in 'Recipients', with: 'irc://chat.freenode.net/#commits'
+ check 'Colorize messages'
+ click_button 'Save'
+ end
+
+ step 'I should see Irker service settings saved' do
+ find_field('Recipients').value.should == 'irc://chat.freenode.net/#commits'
+ find_field('Colorize messages').value.should == '1'
+ end
+
step 'I click Slack service link' do
click_link 'Slack'
end
step 'I fill Slack settings' do
check 'Active'
- fill_in 'Subdomain', with: 'gitlab'
- fill_in 'Room', with: '#gitlab'
- fill_in 'Token', with: 'verySecret'
+ fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
click_button 'Save'
end
step 'I should see Slack service settings saved' do
- find_field('Subdomain').value.should == 'gitlab'
- find_field('Room').value.should == '#gitlab'
- find_field('Token').value.should == 'verySecret'
+ find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
+ end
+
+ step 'I click Pushover service link' do
+ click_link 'Pushover'
+ end
+
+ step 'I fill Pushover settings' do
+ check 'Active'
+ fill_in 'Api key', with: 'verySecret'
+ fill_in 'User key', with: 'verySecret'
+ fill_in 'Device', with: 'myDevice'
+ select 'High Priority', from: 'Priority'
+ select 'Bike', from: 'Sound'
+ click_button 'Save'
+ end
+
+ step 'I should see Pushover service settings saved' do
+ find_field('Api key').value.should == 'verySecret'
+ find_field('User key').value.should == 'verySecret'
+ find_field('Device').value.should == 'myDevice'
+ find_field('Priority').find('option[selected]').value.should == '1'
+ find_field('Sound').find('option[selected]').value.should == 'bike'
+ end
+
+ step 'I click Atlassian Bamboo CI service link' do
+ click_link 'Atlassian Bamboo CI'
+ end
+
+ step 'I fill Atlassian Bamboo CI settings' do
+ check 'Active'
+ fill_in 'Bamboo url', with: 'http://bamboo.example.com'
+ fill_in 'Build key', with: 'KEY'
+ fill_in 'Username', with: 'user'
+ fill_in 'Password', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Atlassian Bamboo CI service settings saved' do
+ find_field('Bamboo url').value.should == 'http://bamboo.example.com'
+ find_field('Build key').value.should == 'KEY'
+ find_field('Username').value.should == 'user'
+ end
+
+ step 'I click JetBrains TeamCity CI service link' do
+ click_link 'JetBrains TeamCity CI'
+ end
+
+ step 'I fill JetBrains TeamCity CI settings' do
+ check 'Active'
+ fill_in 'Teamcity url', with: 'http://teamcity.example.com'
+ fill_in 'Build type', with: 'GitlabTest_Build'
+ fill_in 'Username', with: 'user'
+ fill_in 'Password', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see JetBrains TeamCity CI service settings saved' do
+ find_field('Teamcity url').value.should == 'http://teamcity.example.com'
+ find_field('Build type').value.should == 'GitlabTest_Build'
+ find_field('Username').value.should == 'user'
end
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index feae535fbe..343aeb53b1 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -1,10 +1,10 @@
-class ProjectSnippets < Spinach::FeatureSteps
+class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedNote
include SharedPaths
- And 'project "Shop" have "Snippet one" snippet' do
+ step 'project "Shop" have "Snippet one" snippet' do
create(:project_snippet,
title: "Snippet one",
content: "Test content",
@@ -13,7 +13,7 @@ class ProjectSnippets < Spinach::FeatureSteps
author: project.users.first)
end
- And 'project "Shop" have no "Snippet two" snippet' do
+ step 'project "Shop" have no "Snippet two" snippet' do
create(:snippet,
title: "Snippet two",
content: "Test content",
@@ -21,37 +21,37 @@ class ProjectSnippets < Spinach::FeatureSteps
author: project.users.first)
end
- Given 'I click link "New Snippet"' do
+ step 'I click link "New Snippet"' do
click_link "Add new snippet"
end
- Given 'I click link "Snippet one"' do
+ step 'I click link "Snippet one"' do
click_link "Snippet one"
end
- Then 'I should see "Snippet one" in snippets' do
+ step 'I should see "Snippet one" in snippets' do
page.should have_content "Snippet one"
end
- And 'I should not see "Snippet two" in snippets' do
+ step 'I should not see "Snippet two" in snippets' do
page.should_not have_content "Snippet two"
end
- And 'I should not see "Snippet one" in snippets' do
+ step 'I should not see "Snippet one" in snippets' do
page.should_not have_content "Snippet one"
end
- And 'I click link "Edit"' do
+ step 'I click link "Edit"' do
within ".file-title" do
click_link "Edit"
end
end
- And 'I click link "Remove Snippet"' do
+ step 'I click link "Remove Snippet"' do
click_link "remove"
end
- And 'I submit new snippet "Snippet three"' do
+ step 'I submit new snippet "Snippet three"' do
fill_in "project_snippet_title", :with => "Snippet three"
fill_in "project_snippet_file_name", :with => "my_snippet.rb"
within('.file-editor') do
@@ -60,37 +60,33 @@ class ProjectSnippets < Spinach::FeatureSteps
click_button "Create snippet"
end
- Then 'I should see snippet "Snippet three"' do
+ step 'I should see snippet "Snippet three"' do
page.should have_content "Snippet three"
page.should have_content "Content of snippet three"
end
- And 'I submit new title "Snippet new title"' do
+ step 'I submit new title "Snippet new title"' do
fill_in "project_snippet_title", :with => "Snippet new title"
click_button "Save"
end
- Then 'I should see "Snippet new title"' do
+ step 'I should see "Snippet new title"' do
page.should have_content "Snippet new title"
end
- And 'I leave a comment like "Good snippet!"' do
+ step 'I leave a comment like "Good snippet!"' do
within('.js-main-target-form') do
fill_in "note_note", with: "Good snippet!"
click_button "Add Comment"
end
end
- Then 'I should see comment "Good snippet!"' do
+ step 'I should see comment "Good snippet!"' do
page.should have_content "Good snippet!"
end
- And 'I visit snippet page "Snippet one"' do
- visit project_snippet_path(project, project_snippet)
- end
-
- def project
- @project ||= Project.find_by!(name: "Shop")
+ step 'I visit snippet page "Snippet one"' do
+ visit namespace_project_snippet_path(project.namespace, project, project_snippet)
end
def project_snippet
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
new file mode 100644
index 0000000000..caf6c73ee0
--- /dev/null
+++ b/features/steps/project/source/browse_files.rb
@@ -0,0 +1,218 @@
+class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+ include RepoHelpers
+
+ step 'I should see files from repository' do
+ page.should have_content "VERSION"
+ page.should have_content ".gitignore"
+ page.should have_content "LICENSE"
+ end
+
+ step 'I should see files from repository for "6d39438"' do
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "6d39438")
+ page.should have_content ".gitignore"
+ page.should have_content "LICENSE"
+ end
+
+ step 'I see the ".gitignore"' do
+ page.should have_content '.gitignore'
+ end
+
+ step 'I don\'t see the ".gitignore"' do
+ page.should_not have_content '.gitignore'
+ end
+
+ step 'I click on ".gitignore" file in repo' do
+ click_link ".gitignore"
+ end
+
+ step 'I should see its content' do
+ page.should have_content old_gitignore_content
+ end
+
+ step 'I should see its new content' do
+ page.should have_content new_gitignore_content
+ end
+
+ step 'I click link "Raw"' do
+ click_link 'Raw'
+ end
+
+ step 'I should see raw file content' do
+ source.should == sample_blob.data
+ end
+
+ step 'I click button "Edit"' do
+ click_link 'Edit'
+ end
+
+ step 'I cannot see the edit button' do
+ page.should_not have_link 'edit'
+ end
+
+ step 'The edit button is disabled' do
+ page.should have_css '.disabled', text: 'Edit'
+ end
+
+ step 'I can edit code' do
+ set_new_content
+ evaluate_script('blob.editor.getValue()').should == new_gitignore_content
+ end
+
+ step 'I edit code' do
+ set_new_content
+ end
+
+ step 'I fill the new file name' do
+ fill_in :file_name, with: new_file_name
+ end
+
+ step 'I fill the new branch name' do
+ fill_in :new_branch, with: 'new_branch_name'
+ end
+
+ step 'I fill the new file name with an illegal name' do
+ fill_in :file_name, with: 'Spaces Not Allowed'
+ end
+
+ step 'I fill the commit message' do
+ fill_in :commit_message, with: 'Not yet a commit message.'
+ end
+
+ step 'I click link "Diff"' do
+ click_link 'Preview changes'
+ end
+
+ step 'I click on "Commit Changes"' do
+ click_button 'Commit Changes'
+ end
+
+ step 'I click on "Remove"' do
+ click_button 'Remove'
+ end
+
+ step 'I click on "Remove file"' do
+ click_button 'Remove file'
+ end
+
+ step 'I see diff' do
+ page.should have_css '.line_holder.new'
+ end
+
+ step 'I click on "new file" link in repo' do
+ click_link 'new-file-link'
+ end
+
+ step 'I can see new file page' do
+ page.should have_content "New file"
+ page.should have_content "Commit message"
+ end
+
+ step 'I click on files directory' do
+ click_link 'files'
+ end
+
+ step 'I click on History link' do
+ click_link 'History'
+ end
+
+ step 'I see Browse dir link' do
+ page.should have_link 'Browse Dir »'
+ page.should_not have_link 'Browse Code »'
+ end
+
+ step 'I click on readme file' do
+ within '.tree-table' do
+ click_link 'README.md'
+ end
+ end
+
+ step 'I see Browse file link' do
+ page.should have_link 'Browse File »'
+ page.should_not have_link 'Browse Code »'
+ end
+
+ step 'I see Browse code link' do
+ page.should have_link 'Browse Code »'
+ page.should_not have_link 'Browse File »'
+ page.should_not have_link 'Browse Dir »'
+ end
+
+ step 'I click on Permalink' do
+ click_link 'Permalink'
+ end
+
+ step 'I am redirected to the files URL' do
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, 'master')
+ end
+
+ step 'I am redirected to the ".gitignore"' do
+ expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore'))
+ end
+
+ step 'I am redirected to the ".gitignore" on new branch' do
+ expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/.gitignore'))
+ end
+
+ step 'I am redirected to the permalink URL' do
+ expect(current_path).to(
+ eq(namespace_project_blob_path(@project.namespace, @project,
+ @project.repository.commit.sha +
+ '/.gitignore'))
+ )
+ end
+
+ step 'I am redirected to the new file' do
+ expect(current_path).to eq(namespace_project_blob_path(
+ @project.namespace, @project, 'master/' + new_file_name))
+ end
+
+ step 'I am redirected to the new file on new branch' do
+ expect(current_path).to eq(namespace_project_blob_path(
+ @project.namespace, @project, 'new_branch_name/' + new_file_name))
+ end
+
+ step "I don't see the permalink link" do
+ expect(page).not_to have_link('permalink')
+ end
+
+ step 'I see a commit error message' do
+ expect(page).to have_content('Your changes could not be committed')
+ end
+
+ step 'I create bare repo' do
+ click_link 'Create empty bare repository'
+ end
+
+ step 'I click on "add a file" link' do
+ click_link 'add a file'
+
+ # Remove pre-receive hook so we can push without auth
+ FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
+ end
+
+ private
+
+ def set_new_content
+ execute_script("blob.editor.setValue('#{new_gitignore_content}')")
+ end
+
+ # Content of the gitignore file on the seed repository.
+ def old_gitignore_content
+ '*.rbc'
+ end
+
+ # Constant value that differs from the content
+ # of the gitignore of the seed repository.
+ def new_gitignore_content
+ old_gitignore_content + 'a'
+ end
+
+ # Constant value that is a valid filename and
+ # not a filename present at root of the seed repository.
+ def new_file_name
+ 'not_a_file.md'
+ end
+end
diff --git a/features/steps/project/browse_git_repo.rb b/features/steps/project/source/git_blame.rb
similarity index 54%
rename from features/steps/project/browse_git_repo.rb
rename to features/steps/project/source/git_blame.rb
index 2c3017dd4e..e29a816c51 100644
--- a/features/steps/project/browse_git_repo.rb
+++ b/features/steps/project/source/git_blame.rb
@@ -1,17 +1,17 @@
-class ProjectBrowseGitRepo < Spinach::FeatureSteps
+class Spinach::Features::ProjectSourceGitBlame < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
- Given 'I click on ".gitignore" file in repo' do
+ step 'I click on ".gitignore" file in repo' do
click_link ".gitignore"
end
- And 'I click blame button' do
- click_link "blame"
+ step 'I click Blame button' do
+ click_link 'Blame'
end
- Then 'I should see git file blame' do
+ step 'I should see git file blame' do
page.should have_content "*.rb"
page.should have_content "Dmitriy Zaporozhets"
page.should have_content "Initial commit"
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
new file mode 100644
index 0000000000..7961fdedad
--- /dev/null
+++ b/features/steps/project/source/markdown_render.rb
@@ -0,0 +1,288 @@
+# If you need to modify the existing seed repository for your tests,
+# it is recommended that you make the changes on the `markdown` branch of the seed project repository,
+# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info.
+class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedMarkdown
+
+ step 'I own project "Delta"' do
+ @project = Project.find_by(name: "Delta")
+ @project ||= create(:project, name: "Delta", namespace: @user.namespace)
+ @project.team << [@user, :master]
+ end
+
+ step 'I should see files from repository in markdown' do
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown")
+ page.should have_content "README.md"
+ page.should have_content "CHANGELOG"
+ end
+
+ step 'I should see rendered README which contains correct links' do
+ page.should have_content "Welcome to GitLab GitLab is a free project and repository management application"
+ page.should have_link "GitLab API doc"
+ page.should have_link "GitLab API website"
+ page.should have_link "Rake tasks"
+ page.should have_link "backup and restore procedure"
+ page.should have_link "GitLab API doc directory"
+ page.should have_link "Maintenance"
+ end
+
+ step 'I click on Gitlab API in README' do
+ click_link "GitLab API doc"
+ end
+
+ step 'I should see correct document rendered' do
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ page.should have_content "All API requests require authentication"
+ end
+
+ step 'I click on Rake tasks in README' do
+ click_link "Rake tasks"
+ end
+
+ step 'I should see correct directory rendered' do
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+ page.should have_content "backup_restore.md"
+ page.should have_content "maintenance.md"
+ end
+
+ step 'I click on GitLab API doc directory in README' do
+ click_link "GitLab API doc directory"
+ end
+
+ step 'I should see correct doc/api directory rendered' do
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ page.should have_content "README.md"
+ page.should have_content "users.md"
+ end
+
+ step 'I click on Maintenance in README' do
+ click_link "Maintenance"
+ end
+
+ step 'I should see correct maintenance file rendered' do
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md")
+ page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
+ end
+
+ step 'I click on link "empty" in the README' do
+ within('.readme-holder') do
+ click_link "empty"
+ end
+ end
+
+ step 'I click on link "id" in the README' do
+ within('.readme-holder') do
+ click_link "#id"
+ end
+ end
+
+ step 'I navigate to the doc/api/README' do
+ within '.tree-table' do
+ click_link "doc"
+ end
+
+ within '.tree-table' do
+ click_link "api"
+ end
+
+ within '.tree-table' do
+ click_link "README.md"
+ end
+ end
+
+ step 'I see correct file rendered' do
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ page.should have_content "Contents"
+ page.should have_link "Users"
+ page.should have_link "Rake tasks"
+ end
+
+ step 'I click on users in doc/api/README' do
+ click_link "Users"
+ end
+
+ step 'I should see the correct document file' do
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ page.should have_content "Get a list of users."
+ end
+
+ step 'I click on raketasks in doc/api/README' do
+ click_link "Rake tasks"
+ end
+
+ # Markdown branch
+
+ When 'I visit markdown branch' do
+ visit namespace_project_tree_path(@project.namespace, @project, "markdown")
+ end
+
+ When 'I visit markdown branch "README.md" blob' do
+ visit namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
+ end
+
+ When 'I visit markdown branch "d" tree' do
+ visit namespace_project_tree_path(@project.namespace, @project, "markdown/d")
+ end
+
+ When 'I visit markdown branch "d/README.md" blob' do
+ visit namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
+ end
+
+ step 'I should see files from repository in markdown branch' do
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown")
+ page.should have_content "README.md"
+ page.should have_content "CHANGELOG"
+ end
+
+ step 'I see correct file rendered in markdown branch' do
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ page.should have_content "Contents"
+ page.should have_link "Users"
+ page.should have_link "Rake tasks"
+ end
+
+ step 'I should see correct document rendered for markdown branch' do
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ page.should have_content "All API requests require authentication"
+ end
+
+ step 'I should see correct directory rendered for markdown branch' do
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+ page.should have_content "backup_restore.md"
+ page.should have_content "maintenance.md"
+ end
+
+ step 'I should see the users document file in markdown branch' do
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ page.should have_content "Get a list of users."
+ end
+
+ # Expected link contents
+
+ step 'The link with text "empty" should have url "tree/markdown"' do
+ find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown")
+ end
+
+ step 'The link with text "empty" should have url "blob/markdown/README.md"' do
+ find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
+ end
+
+ step 'The link with text "empty" should have url "tree/markdown/d"' do
+ find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown/d")
+ end
+
+ step 'The link with text "empty" should have '\
+ 'url "blob/markdown/d/README.md"' do
+ find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
+ end
+
+ step 'The link with text "ID" should have url "tree/markdownID"' do
+ find('a', text: /^#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
+ end
+
+ step 'The link with text "/ID" should have url "tree/markdownID"' do
+ find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
+ end
+
+ step 'The link with text "README.mdID" '\
+ 'should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ end
+
+ step 'The link with text "d/README.mdID" should have '\
+ 'url "blob/markdown/d/README.mdID"' do
+ find('a', text: /^d\/README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "d/markdown/README.md") + '#id'
+ end
+
+ step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ end
+
+ step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ end
+
+ # Wiki
+
+ step 'I go to wiki page' do
+ click_link "Wiki"
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
+ end
+
+ step 'I add various links to the wiki page' do
+ fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n"
+ fill_in "wiki[message]", with: "Adding links to wiki"
+ click_button "Create page"
+ end
+
+ step 'Wiki page should have added links' do
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
+ page.should have_content "test GitLab API doc Rake tasks"
+ end
+
+ step 'I add a header to the wiki page' do
+ fill_in "wiki[content]", with: "# Wiki header\n"
+ fill_in "wiki[message]", with: "Add header to wiki"
+ click_button "Create page"
+ end
+
+ step 'Wiki header should have correct id and link' do
+ header_should_have_correct_id_and_link(1, 'Wiki header', 'wiki-header')
+ end
+
+ step 'I click on test link' do
+ click_link "test"
+ end
+
+ step 'I see new wiki page named test' do
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "test")
+ page.should have_content "Editing"
+ end
+
+ When 'I go back to wiki page home' do
+ visit namespace_project_wiki_path(@project.namespace, @project, "home")
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
+ end
+
+ step 'I click on GitLab API doc link' do
+ click_link "GitLab API"
+ end
+
+ step 'I see Gitlab API document' do
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "api")
+ page.should have_content "Editing"
+ end
+
+ step 'I click on Rake tasks link' do
+ click_link "Rake tasks"
+ end
+
+ step 'I see Rake tasks directory' do
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "raketasks")
+ page.should have_content "Editing"
+ end
+
+ step 'I go directory which contains README file' do
+ visit namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ end
+
+ step 'I click on a relative link in README' do
+ click_link "Users"
+ end
+
+ step 'I should see the correct markdown' do
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ page.should have_content "List users"
+ end
+
+ step 'Header "Application details" should have correct id and link' do
+ header_should_have_correct_id_and_link(2, 'Application details', 'application-details')
+ end
+
+ step 'Header "GitLab API" should have correct id and link' do
+ header_should_have_correct_id_and_link(1, 'GitLab API', 'gitlab-api')
+ end
+end
diff --git a/features/steps/project/multiselect_blob.rb b/features/steps/project/source/multiselect_blob.rb
similarity index 88%
rename from features/steps/project/multiselect_blob.rb
rename to features/steps/project/source/multiselect_blob.rb
index d4dc118697..b749ba4937 100644
--- a/features/steps/project/multiselect_blob.rb
+++ b/features/steps/project/source/multiselect_blob.rb
@@ -1,4 +1,4 @@
-class ProjectMultiselectBlob < Spinach::FeatureSteps
+class Spinach::Features::ProjectSourceMultiselectBlob < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
@@ -12,7 +12,7 @@ class ProjectMultiselectBlob < Spinach::FeatureSteps
step "I shift-click line #{line_number} in file" do
script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));"
- page.evaluate_script(script)
+ execute_script(script)
end
end
end
@@ -45,11 +45,11 @@ class ProjectMultiselectBlob < Spinach::FeatureSteps
check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5)
step 'I go back in history' do
- page.evaluate_script("window.history.back()")
+ go_back
end
step 'I go forward in history' do
- page.evaluate_script("window.history.forward()")
+ go_forward
end
step 'I click on ".gitignore" file in repo' do
diff --git a/features/steps/project/search_code.rb b/features/steps/project/source/search_code.rb
similarity index 72%
rename from features/steps/project/search_code.rb
rename to features/steps/project/source/search_code.rb
index affa7d3b43..9c2864cc93 100644
--- a/features/steps/project/search_code.rb
+++ b/features/steps/project/source/search_code.rb
@@ -1,4 +1,4 @@
-class ProjectSearchCode < Spinach::FeatureSteps
+class Spinach::Features::ProjectSourceSearchCode < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
@@ -6,7 +6,6 @@ class ProjectSearchCode < Spinach::FeatureSteps
step 'I search for term "coffee"' do
fill_in "search", with: "coffee"
click_button "Go"
- click_link 'Repository Code'
end
step 'I should see files from repository containing "coffee"' do
@@ -15,6 +14,6 @@ class ProjectSearchCode < Spinach::FeatureSteps
end
step 'I should see empty result' do
- page.should have_content "We couldn't find any matching code"
+ page.should have_content "We couldn't find any matching"
end
end
diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb
index 562df04e34..50cdfd73c3 100644
--- a/features/steps/project/star.rb
+++ b/features/steps/project/star.rb
@@ -22,12 +22,16 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
# Requires @javascript
step "I click on the star toggle button" do
- page.find(".star .toggle", visible: true).click
+ find(".star-btn", visible: true).click
+ end
+
+ step 'I redirected to sign in page' do
+ current_path.should == new_user_session_path
end
protected
def has_n_stars(n)
- expect(page).to have_css(".star .count", text: /^#{n}$/, visible: true)
+ expect(page).to have_css(".star-btn .count", text: n, visible: true)
end
end
diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb
index ffc5016529..e95621071c 100644
--- a/features/steps/project/team_management.rb
+++ b/features/steps/project/team_management.rb
@@ -1,95 +1,115 @@
-class ProjectTeamManagement < Spinach::FeatureSteps
+class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
include Select2Helper
- Then 'I should be able to see myself in team' do
+ step 'I should be able to see myself in team' do
page.should have_content(@user.name)
page.should have_content(@user.username)
end
- And 'I should see "Sam" in team list' do
+ step 'I should see "Sam" in team list' do
user = User.find_by(name: "Sam")
page.should have_content(user.name)
page.should have_content(user.username)
end
- Given 'I click link "New Team Member"' do
- click_link "New project member"
+ step 'I click link "Add members"' do
+ find(:css, 'button.btn-new').click
end
- And 'I select "Mike" as "Reporter"' do
+ step 'I select "Mike" as "Reporter"' do
user = User.find_by(name: "Mike")
- select2(user.id, from: "#user_ids", multiple: true)
- within "#new_team_member" do
- select "Reporter", from: "project_access"
+ within ".users-project-form" do
+ select2(user.id, from: "#user_ids", multiple: true)
+ select "Reporter", from: "access_level"
end
- click_button "Add users"
+ click_button "Add users to project"
end
- Then 'I should see "Mike" in team list as "Reporter"' do
+ step 'I should see "Mike" in team list as "Reporter"' do
within ".access-reporter" do
page.should have_content('Mike')
end
end
- Given 'I should see "Sam" in team list as "Developer"' do
+ step 'I select "sjobs@apple.com" as "Reporter"' do
+ within ".users-project-form" do
+ select2("sjobs@apple.com", from: "#user_ids", multiple: true)
+ select "Reporter", from: "access_level"
+ end
+ click_button "Add users to project"
+ end
+
+ step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
+ within ".access-reporter" do
+ page.should have_content('sjobs@apple.com')
+ page.should have_content('invited')
+ page.should have_content('Reporter')
+ end
+ end
+
+ step 'I should see "Sam" in team list as "Developer"' do
within ".access-developer" do
page.should have_content('Sam')
end
end
- And 'I change "Sam" role to "Reporter"' do
- user = User.find_by(name: "Sam")
- within "#user_#{user.id}" do
- select "Reporter", from: "team_member_project_access"
+ step 'I change "Sam" role to "Reporter"' do
+ project = Project.find_by(name: "Shop")
+ user = User.find_by(name: 'Sam')
+ project_member = project.project_members.find_by(user_id: user.id)
+ within "#project_member_#{project_member.id}" do
+ click_button "Edit access level"
+ select "Reporter", from: "project_member_access_level"
+ click_button "Save"
end
end
- And 'I should see "Sam" in team list as "Reporter"' do
+ step 'I should see "Sam" in team list as "Reporter"' do
within ".access-reporter" do
page.should have_content('Sam')
end
end
- And 'I click link "Remove from team"' do
+ step 'I click link "Remove from team"' do
click_link "Remove from team"
end
- And 'I should not see "Sam" in team list' do
+ step 'I should not see "Sam" in team list' do
user = User.find_by(name: "Sam")
page.should_not have_content(user.name)
page.should_not have_content(user.username)
end
- And 'gitlab user "Mike"' do
+ step 'gitlab user "Mike"' do
create(:user, name: "Mike")
end
- And 'gitlab user "Sam"' do
+ step 'gitlab user "Sam"' do
create(:user, name: "Sam")
end
- And '"Sam" is "Shop" developer' do
+ step '"Sam" is "Shop" developer' do
user = User.find_by(name: "Sam")
project = Project.find_by(name: "Shop")
project.team << [user, :developer]
end
- Given 'I own project "Website"' do
+ step 'I own project "Website"' do
@project = create(:empty_project, name: "Website", namespace: @user.namespace)
@project.team << [@user, :master]
end
- And '"Mike" is "Website" reporter' do
+ step '"Mike" is "Website" reporter' do
user = User.find_by(name: "Mike")
project = Project.find_by(name: "Website")
project.team << [user, :reporter]
end
- And 'I click link "Import team from another project"' do
+ step 'I click link "Import team from another project"' do
click_link "Import members from another project"
end
@@ -100,7 +120,10 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
step 'I click cancel link for "Sam"' do
- within "#user_#{User.find_by(name: 'Sam').id}" do
+ project = Project.find_by(name: "Shop")
+ user = User.find_by(name: 'Sam')
+ project_member = project.project_members.find_by(user_id: user.id)
+ within "#project_member_#{project_member.id}" do
click_link('Remove user from team')
end
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 96f2505d24..bb93e582a1 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -3,24 +3,24 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
include SharedProject
include SharedNote
include SharedPaths
+ include WikiHelper
- Given 'I click on the Cancel button' do
+ step 'I click on the Cancel button' do
within(:css, ".form-actions") do
click_on "Cancel"
end
end
- Then 'I should be redirected back to the Edit Home Wiki page' do
- url = URI.parse(current_url)
- url.path.should == project_wiki_path(project, :home)
+ step 'I should be redirected back to the Edit Home Wiki page' do
+ current_path.should == namespace_project_wiki_path(project.namespace, project, :home)
end
- Given 'I create the Wiki Home page' do
+ step 'I create the Wiki Home page' do
fill_in "wiki_content", with: '[link test](test)'
click_on "Create page"
end
- Then 'I should see the newly created wiki page' do
+ step 'I should see the newly created wiki page' do
page.should have_content "Home"
page.should have_content "link test"
@@ -28,74 +28,73 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
page.should have_content "Editing"
end
- Given 'I have an existing Wiki page' do
+ step 'I have an existing Wiki page' do
wiki.create_page("existing", "content", :markdown, "first commit")
@page = wiki.find_page("existing")
end
- And 'I browse to that Wiki page' do
- visit project_wiki_path(project, @page)
+ step 'I browse to that Wiki page' do
+ visit namespace_project_wiki_path(project.namespace, project, @page)
end
- And 'I click on the Edit button' do
+ step 'I click on the Edit button' do
click_on "Edit"
end
- And 'I change the content' do
+ step 'I change the content' do
fill_in "Content", with: 'Updated Wiki Content'
click_on "Save changes"
end
- Then 'I should see the updated content' do
+ step 'I should see the updated content' do
page.should have_content "Updated Wiki Content"
end
- Then 'I should be redirected back to that Wiki page' do
- url = URI.parse(current_url)
- url.path.should == project_wiki_path(project, @page)
+ step 'I should be redirected back to that Wiki page' do
+ current_path.should == namespace_project_wiki_path(project.namespace, project, @page)
end
- And 'That page has two revisions' do
+ step 'That page has two revisions' do
@page.update("new content", :markdown, "second commit")
end
- And 'I click the History button' do
+ step 'I click the History button' do
click_on "History"
end
- Then 'I should see both revisions' do
+ step 'I should see both revisions' do
page.should have_content current_user.name
page.should have_content "first commit"
page.should have_content "second commit"
end
- And 'I click on the "Delete this page" button' do
+ step 'I click on the "Delete this page" button' do
click_on "Delete this page"
end
- Then 'The page should be deleted' do
+ step 'The page should be deleted' do
page.should have_content "Page was successfully deleted"
end
- And 'I click on the "Pages" button' do
+ step 'I click on the "Pages" button' do
click_on "Pages"
end
- Then 'I should see the existing page in the pages list' do
+ step 'I should see the existing page in the pages list' do
page.should have_content current_user.name
page.should have_content @page.title
end
- Given 'I have an existing Wiki page with images linked on page' do
+ step 'I have an existing Wiki page with images linked on page' do
wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ![image](image.jpg)", :markdown, "first commit")
@wiki_page = wiki.find_page("pictures")
end
- And 'I browse to wiki page with images' do
- visit project_wiki_path(project, @wiki_page)
+ step 'I browse to wiki page with images' do
+ visit namespace_project_wiki_path(project.namespace, project, @wiki_page)
end
- And 'I click on existing image link' do
+ step 'I click on existing image link' do
file = Gollum::File.new(wiki.wiki)
Gollum::Wiki.any_instance.stub(:file).with("image.jpg", "master", true).and_return(file)
Gollum::File.any_instance.stub(:mime_type).and_return("image/jpeg")
@@ -103,30 +102,63 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
click_on "image"
end
- Then 'I should see the image from wiki repo' do
- url = URI.parse(current_url)
- url.path.should match("wikis/image.jpg")
+ step 'I should see the image from wiki repo' do
+ current_path.should match('wikis/image.jpg')
page.should_not have_xpath('/html') # Page should render the image which means there is no html involved
Gollum::Wiki.any_instance.unstub(:file)
Gollum::File.any_instance.unstub(:mime_type)
end
- Then 'Image should be shown on the page' do
+ step 'Image should be shown on the page' do
page.should have_xpath("//img[@src=\"image.jpg\"]")
end
- And 'I click on image link' do
+ step 'I click on image link' do
page.should have_link('image', href: "image.jpg")
click_on "image"
end
- Then 'I should see the new wiki page form' do
- url = URI.parse(current_url)
- url.path.should match("wikis/image.jpg")
+ step 'I should see the new wiki page form' do
+ current_path.should match('wikis/image.jpg')
page.should have_content('New Wiki Page')
page.should have_content('Editing - image.jpg')
end
+ step 'I create a New page with paths' do
+ click_on 'New Page'
+ fill_in 'Page slug', with: 'one/two/three'
+ click_on 'Build'
+ fill_in "wiki_content", with: 'wiki content'
+ click_on "Create page"
+ current_path.should include 'one/two/three'
+ end
+
+ step 'I should see non-escaped link in the pages list' do
+ page.should have_xpath("//a[@href='/#{project.path_with_namespace}/wikis/one/two/three']")
+ end
+
+ step 'I edit the Wiki page with a path' do
+ click_on 'three'
+ click_on 'Edit'
+ end
+
+ step 'I should see a non-escaped path' do
+ current_path.should include 'one/two/three'
+ end
+
+ step 'I should see the Editing page' do
+ page.should have_content('Editing')
+ end
+
+ step 'I view the page history of a Wiki page that has a path' do
+ click_on 'three'
+ click_on 'Page History'
+ end
+
+ step 'I should see the page history' do
+ page.should have_content('History for')
+ end
+
def wiki
@project_wiki = ProjectWiki.new(project, current_user)
end
diff --git a/features/steps/search.rb b/features/steps/search.rb
new file mode 100644
index 0000000000..6f0e038c4d
--- /dev/null
+++ b/features/steps/search.rb
@@ -0,0 +1,69 @@
+class Spinach::Features::Search < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ step 'I search for "Sho"' do
+ fill_in "dashboard_search", with: "Sho"
+ click_button "Search"
+ end
+
+ step 'I search for "Foo"' do
+ fill_in "dashboard_search", with: "Foo"
+ click_button "Search"
+ end
+
+ step 'I search for "rspec"' do
+ fill_in "dashboard_search", with: "rspec"
+ click_button "Search"
+ end
+
+ step 'I click "Issues" link' do
+ within '.search-filter' do
+ click_link 'Issues'
+ end
+ end
+
+ step 'I click project "Shop" link' do
+ within '.project-filter' do
+ click_link project.name_with_namespace
+ end
+ end
+
+ step 'I click "Merge requests" link' do
+ within '.search-filter' do
+ click_link 'Merge requests'
+ end
+ end
+
+ step 'I should see "Shop" project link' do
+ page.should have_link "Shop"
+ end
+
+ step 'I should see code results for project "Shop"' do
+ page.should have_content 'Update capybara, rspec-rails, poltergeist to recent versions'
+ end
+
+ step 'I search for "Contibuting"' do
+ fill_in "dashboard_search", with: "Contibuting"
+ click_button "Search"
+ end
+
+ step 'project has issues' do
+ create(:issue, title: "Foo", project: project)
+ create(:issue, title: "Bar", project: project)
+ end
+
+ step 'project has merge requests' do
+ create(:merge_request, title: "Foo", source_project: project, target_project: project)
+ create(:merge_request, :simple, title: "Bar", source_project: project, target_project: project)
+ end
+
+ step 'I should see "Foo" link in the search results' do
+ find(:css, '.search-results').should have_link 'Foo'
+ end
+
+ step 'I should not see "Bar" link in the search results' do
+ find(:css, '.search-results').should_not have_link 'Bar'
+ end
+end
diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb
index e3cd5fcfe8..9beb688bd1 100644
--- a/features/steps/shared/active_tab.rb
+++ b/features/steps/shared/active_tab.rb
@@ -2,26 +2,46 @@ module SharedActiveTab
include Spinach::DSL
def ensure_active_main_tab(content)
- page.find('.main-nav li.active').should have_content(content)
+ find('.nav-sidebar > li.active').should have_content(content)
end
def ensure_active_sub_tab(content)
- page.find('div.content ul.nav-tabs li.active').should have_content(content)
+ find('div.content ul.nav-tabs li.active').should have_content(content)
end
def ensure_active_sub_nav(content)
- page.find('div.content ul.nav-stacked-menu li.active').should have_content(content)
+ find('.sidebar-subnav > li.active').should have_content(content)
end
- And 'no other main tabs should be active' do
- page.should have_selector('.main-nav li.active', count: 1)
+ step 'no other main tabs should be active' do
+ page.should have_selector('.nav-sidebar > li.active', count: 1)
end
- And 'no other sub tabs should be active' do
+ step 'no other sub tabs should be active' do
page.should have_selector('div.content ul.nav-tabs li.active', count: 1)
end
- And 'no other sub navs should be active' do
- page.should have_selector('div.content ul.nav-stacked-menu li.active', count: 1)
+ step 'no other sub navs should be active' do
+ page.should have_selector('.sidebar-subnav > li.active', count: 1)
+ end
+
+ step 'the active main tab should be Home' do
+ ensure_active_main_tab('Your Projects')
+ end
+
+ step 'the active main tab should be Projects' do
+ ensure_active_main_tab('Projects')
+ end
+
+ step 'the active main tab should be Issues' do
+ ensure_active_main_tab('Issues')
+ end
+
+ step 'the active main tab should be Merge Requests' do
+ ensure_active_main_tab('Merge Requests')
+ end
+
+ step 'the active main tab should be Help' do
+ ensure_active_main_tab('Help')
end
end
diff --git a/features/steps/shared/admin.rb b/features/steps/shared/admin.rb
index 1b712dc6d0..b607299567 100644
--- a/features/steps/shared/admin.rb
+++ b/features/steps/shared/admin.rb
@@ -1,11 +1,11 @@
module SharedAdmin
include Spinach::DSL
- And 'there are projects in system' do
+ step 'there are projects in system' do
2.times { create(:project) }
end
- And 'system has users' do
+ step 'system has users' do
2.times { create(:user) }
end
end
diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb
index b48021dc14..ac8a3df6bb 100644
--- a/features/steps/shared/authentication.rb
+++ b/features/steps/shared/authentication.rb
@@ -4,11 +4,11 @@ module SharedAuthentication
include Spinach::DSL
include LoginHelpers
- Given 'I sign in as a user' do
+ step 'I sign in as a user' do
login_as :user
end
- Given 'I sign in as an admin' do
+ step 'I sign in as an admin' do
login_as :admin
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index b107b08322..510e0f0f93 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -2,24 +2,24 @@ module SharedDiffNote
include Spinach::DSL
include RepoHelpers
- Given 'I cancel the diff comment' do
+ step 'I cancel the diff comment' do
within(diff_file_selector) do
find(".js-close-discussion-note-form").click
end
end
- Given 'I delete a diff comment' do
+ step 'I delete a diff comment' do
find('.note').hover
find(".js-note-delete").click
end
- Given 'I haven\'t written any diff comment text' do
+ step 'I haven\'t written any diff comment text' do
within(diff_file_selector) do
fill_in "note[note]", with: ""
end
end
- Given 'I leave a diff comment like "Typo, please fix"' do
+ step 'I leave a diff comment like "Typo, please fix"' do
click_diff_line(sample_commit.line_code)
within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Typo, please fix"
@@ -28,130 +28,132 @@ module SharedDiffNote
end
end
- Given 'I preview a diff comment text like "Should fix it :smile:"' do
+ step 'I preview a diff comment text like "Should fix it :smile:"' do
click_diff_line(sample_commit.line_code)
within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Should fix it :smile:"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
- Given 'I preview another diff comment text like "DRY this up"' do
+ step 'I preview another diff comment text like "DRY this up"' do
click_diff_line(sample_commit.del_line_code)
within("#{diff_file_selector} form[rel$='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "DRY this up"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
- Given 'I open a diff comment form' do
+ step 'I open a diff comment form' do
click_diff_line(sample_commit.line_code)
end
- Given 'I open another diff comment form' do
+ step 'I open another diff comment form' do
click_diff_line(sample_commit.del_line_code)
end
- Given 'I write a diff comment like ":-1: I don\'t like this"' do
+ step 'I write a diff comment like ":-1: I don\'t like this"' do
within(diff_file_selector) do
fill_in "note[note]", with: ":-1: I don\'t like this"
end
end
- Given 'I submit the diff comment' do
+ step 'I submit the diff comment' do
within(diff_file_selector) do
click_button("Add Comment")
end
end
- Then 'I should not see the diff comment form' do
+ step 'I should not see the diff comment form' do
within(diff_file_selector) do
page.should_not have_css("form.new_note")
end
end
- Then 'I should not see the diff comment preview button' do
+ step 'The diff comment preview tab should say there is nothing to do' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview-button", visible: false)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
- Then 'I should not see the diff comment text field' do
+ step 'I should not see the diff comment text field' do
within(diff_file_selector) do
- page.should have_css(".js-note-text", visible: false)
+ expect(find('.js-note-text')).not_to be_visible
end
end
- Then 'I should only see one diff form' do
+ step 'I should only see one diff form' do
within(diff_file_selector) do
page.should have_css("form.new_note", count: 1)
end
end
- Then 'I should see a diff comment form with ":-1: I don\'t like this"' do
+ step 'I should see a diff comment form with ":-1: I don\'t like this"' do
within(diff_file_selector) do
page.should have_field("note[note]", with: ":-1: I don\'t like this")
end
end
- Then 'I should see a diff comment saying "Typo, please fix"' do
+ step 'I should see a diff comment saying "Typo, please fix"' do
within("#{diff_file_selector} .note") do
page.should have_content("Typo, please fix")
end
end
- Then 'I should see a discussion reply button' do
+ step 'I should see a discussion reply button' do
within(diff_file_selector) do
- page.should have_link("Reply")
+ page.should have_button('Reply')
end
end
- Then 'I should see a temporary diff comment form' do
+ step 'I should see a temporary diff comment form' do
within(diff_file_selector) do
page.should have_css(".js-temp-notes-holder form.new_note")
end
end
- Then 'I should see add a diff comment button' do
- page.should have_css(".js-add-diff-note-button", visible: false)
+ step 'I should see add a diff comment button' do
+ page.should have_css('.js-add-diff-note-button', visible: true)
end
- Then 'I should see an empty diff comment form' do
+ step 'I should see an empty diff comment form' do
within(diff_file_selector) do
page.should have_field("note[note]", with: "")
end
end
- Then 'I should see the cancel comment button' do
+ step 'I should see the cancel comment button' do
within("#{diff_file_selector} form") do
page.should have_css(".js-close-discussion-note-form", text: "Cancel")
end
end
- Then 'I should see the diff comment preview' do
+ step 'I should see the diff comment preview' do
within("#{diff_file_selector} form") do
- page.should have_css(".js-note-preview", visible: false)
+ expect(page).to have_css('.js-md-preview', visible: true)
end
end
- Then 'I should see the diff comment edit button' do
+ step 'I should see the diff comment write tab' do
within(diff_file_selector) do
- page.should have_css(".js-note-write-button", visible: true)
+ expect(page).to have_css('.js-md-write-button', visible: true)
end
end
- Then 'I should see the diff comment preview button' do
+ step 'The diff comment preview tab should display rendered Markdown' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview-button", visible: true)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
end
- Then 'I should see two separate previews' do
+ step 'I should see two separate previews' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview", visible: true, count: 2)
- page.should have_content("Should fix it")
- page.should have_content("DRY this up")
+ expect(page).to have_css('.js-md-preview', visible: true, count: 2)
+ expect(page).to have_content('Should fix it')
+ expect(page).to have_content('DRY this up')
end
end
@@ -160,6 +162,6 @@ module SharedDiffNote
end
def click_diff_line(code)
- find("a[data-line-code='#{code}']").click
+ find("button[data-line-code='#{code}']").click
end
end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
new file mode 100644
index 0000000000..41db2612f2
--- /dev/null
+++ b/features/steps/shared/issuable.rb
@@ -0,0 +1,15 @@
+module SharedIssuable
+ include Spinach::DSL
+
+ def edit_issuable
+ find(:css, '.issuable-edit').click
+ end
+
+ step 'I click link "Edit" for the merge request' do
+ edit_issuable
+ end
+
+ step 'I click link "Edit" for the issue' do
+ edit_issuable
+ end
+end
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index 782f3f0920..e71700880c 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -2,11 +2,101 @@ module SharedMarkdown
include Spinach::DSL
def header_should_have_correct_id_and_link(level, text, id, parent = ".wiki")
- page.find(:css, "#{parent} h#{level}##{id}").text.should == text
- page.find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/
+ find(:css, "#{parent} h#{level}##{id}").text.should == text
+ find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/
+ end
+
+ def create_taskable(type, title)
+ desc_text = < .note-text") do
page.should have_content("Comment with a header")
page.should_not have_css("#comment-with-a-header")
end
end
+
+ step 'I leave a comment with task markdown' do
+ within('.js-main-target-form') do
+ fill_in 'note[note]', with: '* [x] Task item'
+ click_button 'Add Comment'
+ sleep 0.05
+ end
+ end
+
+ step 'I should not see task checkboxes in the comment' do
+ expect(page).not_to have_selector(
+ 'li.note div.timeline-content input[type="checkbox"]'
+ )
+ end
+
+ step 'I edit the last comment with a +1' do
+ find(".note").hover
+ find('.js-note-edit').click
+
+ within(".current-note-edit-form") do
+ fill_in 'note[note]', with: '+1 Awesome!'
+ click_button 'Save Comment'
+ sleep 0.05
+ end
+ end
+
+ step 'I should see +1 in the description' do
+ within(".note") do
+ page.should have_content("+1 Awesome!")
+ end
+ end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 0d06383509..e3cf1b92cd 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -1,6 +1,7 @@
module SharedPaths
include Spinach::DSL
include RepoHelpers
+ include DashboardHelper
step 'I visit new project page' do
visit new_project_path
@@ -31,7 +32,7 @@ module SharedPaths
end
step 'I visit group "Owned" members page' do
- visit members_group_path(Group.find_by(name:"Owned"))
+ visit group_group_members_path(Group.find_by(name:"Owned"))
end
step 'I visit group "Owned" settings page' do
@@ -51,7 +52,7 @@ module SharedPaths
end
step 'I visit group "Guest" members page' do
- visit members_group_path(Group.find_by(name:"Guest"))
+ visit group_group_members_path(Group.find_by(name:"Guest"))
end
step 'I visit group "Guest" settings page' do
@@ -71,11 +72,11 @@ module SharedPaths
end
step 'I visit dashboard issues page' do
- visit issues_dashboard_path
+ visit assigned_issues_dashboard_path
end
step 'I visit dashboard merge requests page' do
- visit merge_requests_dashboard_path
+ visit assigned_mrs_dashboard_path
end
step 'I visit dashboard search page' do
@@ -86,6 +87,18 @@ module SharedPaths
visit help_path
end
+ step 'I visit dashboard groups page' do
+ visit dashboard_groups_path
+ end
+
+ step 'I should be redirected to the dashboard groups page' do
+ current_path.should == dashboard_groups_path
+ end
+
+ step 'I visit dashboard starred projects page' do
+ visit starred_dashboard_projects_path
+ end
+
# ----------------------------------------
# Profile
# ----------------------------------------
@@ -94,6 +107,10 @@ module SharedPaths
visit profile_path
end
+ step 'I visit profile applications page' do
+ visit applications_profile_path
+ end
+
step 'I visit profile password page' do
visit edit_profile_password_path
end
@@ -114,14 +131,6 @@ module SharedPaths
visit history_profile_path
end
- step 'I visit profile groups page' do
- visit profile_groups_path
- end
-
- step 'I should be redirected to the profile groups page' do
- current_path.should == profile_groups_path
- end
-
# ----------------------------------------
# Admin
# ----------------------------------------
@@ -131,7 +140,7 @@ module SharedPaths
end
step 'I visit admin projects page' do
- visit admin_projects_path
+ visit admin_namespaces_projects_path
end
step 'I visit admin users page' do
@@ -162,59 +171,72 @@ module SharedPaths
visit admin_teams_path
end
+ step 'I visit admin settings page' do
+ visit admin_application_settings_path
+ end
+
+ step 'I visit applications page' do
+ visit admin_applications_path
+ end
+
# ----------------------------------------
# Generic Project
# ----------------------------------------
step "I visit my project's home page" do
- visit project_path(@project)
+ visit namespace_project_path(@project.namespace, @project)
end
step "I visit my project's settings page" do
- visit edit_project_path(@project)
+ visit edit_namespace_project_path(@project.namespace, @project)
end
step "I visit my project's files page" do
- visit project_tree_path(@project, root_ref)
+ visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ end
+
+ step 'I visit a binary file in the repo' do
+ visit namespace_project_blob_path(@project.namespace, @project, File.join(
+ root_ref, 'files/images/logo-black.png'))
end
step "I visit my project's commits page" do
- visit project_commits_path(@project, root_ref, {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, root_ref, {limit: 5})
end
step "I visit my project's commits page for a specific path" do
- visit project_commits_path(@project, root_ref + "/app/models/project.rb", {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, root_ref + "/app/models/project.rb", {limit: 5})
end
step 'I visit my project\'s commits stats page' do
- visit stats_project_repository_path(@project)
+ visit stats_namespace_project_repository_path(@project.namespace, @project)
end
step "I visit my project's network page" do
# Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10)
- visit project_network_path(@project, root_ref)
+ visit namespace_project_network_path(@project.namespace, @project, root_ref)
end
step "I visit my project's issues page" do
- visit project_issues_path(@project)
+ visit namespace_project_issues_path(@project.namespace, @project)
end
step "I visit my project's merge requests page" do
- visit project_merge_requests_path(@project)
+ visit namespace_project_merge_requests_path(@project.namespace, @project)
end
step "I visit my project's wiki page" do
- visit project_wiki_path(@project, :home)
+ visit namespace_project_wiki_path(@project.namespace, @project, :home)
end
step 'I visit project hooks page' do
- visit project_hooks_path(@project)
+ visit namespace_project_hooks_path(@project.namespace, @project)
end
step 'I visit project deploy keys page' do
- visit project_deploy_keys_path(@project)
+ visit namespace_project_deploy_keys_path(@project.namespace, @project)
end
# ----------------------------------------
@@ -222,118 +244,153 @@ module SharedPaths
# ----------------------------------------
step 'I visit project "Shop" page' do
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I visit project "Forked Shop" merge requests page' do
- visit project_merge_requests_path(@forked_project)
+ visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
end
step 'I visit edit project "Shop" page' do
- visit edit_project_path(project)
+ visit edit_namespace_project_path(project.namespace, project)
end
step 'I visit project branches page' do
- visit project_branches_path(@project)
+ visit namespace_project_branches_path(@project.namespace, @project)
end
step 'I visit project protected branches page' do
- visit project_protected_branches_path(@project)
+ visit namespace_project_protected_branches_path(@project.namespace, @project)
end
step 'I visit compare refs page' do
- visit project_compare_index_path(@project)
+ visit namespace_project_compare_index_path(@project.namespace, @project)
end
step 'I visit project commits page' do
- visit project_commits_path(@project, root_ref, {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, root_ref, {limit: 5})
end
step 'I visit project commits page for stable branch' do
- visit project_commits_path(@project, 'stable', {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, 'stable', {limit: 5})
end
step 'I visit project source page' do
- visit project_tree_path(@project, root_ref)
+ visit namespace_project_tree_path(@project.namespace, @project, root_ref)
end
step 'I visit blob file from repo' do
- visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path))
+ visit namespace_project_blob_path(@project.namespace, @project, File.join(sample_commit.id, sample_blob.path))
end
step 'I visit ".gitignore" file in repo' do
- visit project_blob_path(@project, File.join(root_ref, '.gitignore'))
+ visit namespace_project_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore'))
+ end
+
+ step 'I am on the new file page' do
+ current_path.should eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref))
+ end
+
+ step 'I am on the ".gitignore" edit file page' do
+ current_path.should eq(namespace_project_edit_blob_path(
+ @project.namespace, @project, File.join(root_ref, '.gitignore')))
end
step 'I visit project source page for "6d39438"' do
- visit project_tree_path(@project, "6d39438")
+ visit namespace_project_tree_path(@project.namespace, @project, "6d39438")
+ end
+
+ step 'I visit project source page for' \
+ ' "6d394385cf567f80a8fd85055db1ab4c5295806f"' do
+ visit namespace_project_tree_path(@project.namespace, @project,
+ '6d394385cf567f80a8fd85055db1ab4c5295806f')
end
step 'I visit project tags page' do
- visit project_tags_path(@project)
+ visit namespace_project_tags_path(@project.namespace, @project)
end
step 'I visit project commit page' do
- visit project_commit_path(@project, sample_commit.id)
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
end
step 'I visit project "Shop" issues page' do
- visit project_issues_path(project)
+ visit namespace_project_issues_path(project.namespace, project)
end
step 'I visit issue page "Release 0.4"' do
issue = Issue.find_by(title: "Release 0.4")
- visit project_issue_path(issue.project, issue)
+ visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ end
+
+ step 'I visit issue page "Tasks-open"' do
+ issue = Issue.find_by(title: 'Tasks-open')
+ visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ end
+
+ step 'I visit issue page "Tasks-closed"' do
+ issue = Issue.find_by(title: 'Tasks-closed')
+ visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
end
step 'I visit project "Shop" labels page' do
project = Project.find_by(name: 'Shop')
- visit project_labels_path(project)
+ visit namespace_project_labels_path(project.namespace, project)
end
step 'I visit project "Forum" labels page' do
project = Project.find_by(name: 'Forum')
- visit project_labels_path(project)
+ visit namespace_project_labels_path(project.namespace, project)
end
step 'I visit project "Shop" new label page' do
project = Project.find_by(name: 'Shop')
- visit new_project_label_path(project)
+ visit new_namespace_project_label_path(project.namespace, project)
end
step 'I visit project "Forum" new label page' do
project = Project.find_by(name: 'Forum')
- visit new_project_label_path(project)
+ visit new_namespace_project_label_path(project.namespace, project)
end
step 'I visit merge request page "Bug NS-04"' do
mr = MergeRequest.find_by(title: "Bug NS-04")
- visit project_merge_request_path(mr.target_project, mr)
+ visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
step 'I visit merge request page "Bug NS-05"' do
mr = MergeRequest.find_by(title: "Bug NS-05")
- visit project_merge_request_path(mr.target_project, mr)
+ visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ end
+
+ step 'I visit merge request page "MR-task-open"' do
+ mr = MergeRequest.find_by(title: 'MR-task-open')
+ visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ end
+
+ step 'I visit merge request page "MR-task-closed"' do
+ mr = MergeRequest.find_by(title: 'MR-task-closed')
+ visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
step 'I visit project "Shop" merge requests page' do
- visit project_merge_requests_path(project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
step 'I visit forked project "Shop" merge requests page' do
- visit project_merge_requests_path(project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
step 'I visit project "Shop" milestones page' do
- visit project_milestones_path(project)
+ visit namespace_project_milestones_path(project.namespace, project)
end
step 'I visit project "Shop" team page' do
- visit project_team_index_path(project)
+ visit namespace_project_project_members_path(project.namespace, project)
end
step 'I visit project wiki page' do
- visit project_wiki_path(@project, :home)
+ visit namespace_project_wiki_path(@project.namespace, @project, :home)
end
# ----------------------------------------
@@ -342,17 +399,22 @@ module SharedPaths
step 'I visit project "Community" page' do
project = Project.find_by(name: "Community")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ step 'I visit project "Community" source page' do
+ project = Project.find_by(name: 'Community')
+ visit namespace_project_tree_path(project.namespace, project, root_ref)
end
step 'I visit project "Internal" page' do
project = Project.find_by(name: "Internal")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I visit project "Enterprise" page' do
project = Project.find_by(name: "Enterprise")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
# ----------------------------------------
@@ -361,7 +423,7 @@ module SharedPaths
step "I visit empty project page" do
project = Project.find_by(name: "Empty Public Project")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
# ----------------------------------------
@@ -388,15 +450,15 @@ module SharedPaths
# Snippets
# ----------------------------------------
- Given 'I visit project "Shop" snippets page' do
- visit project_snippets_path(project)
+ step 'I visit project "Shop" snippets page' do
+ visit namespace_project_snippets_path(project.namespace, project)
end
- Given 'I visit snippets page' do
+ step 'I visit snippets page' do
visit snippets_path
end
- Given 'I visit new snippet page' do
+ step 'I visit new snippet page' do
visit new_snippet_path
end
@@ -405,14 +467,14 @@ module SharedPaths
end
def project
- project = Project.find_by!(name: "Shop")
+ Project.find_by!(name: 'Shop')
end
# ----------------------------------------
# Errors
# ----------------------------------------
- Then 'page status code should be 404' do
- page.status_code.should == 404
+ step 'page status code should be 404' do
+ status_code.should == 404
end
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index c131976614..b60ac5e342 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -2,37 +2,49 @@ module SharedProject
include Spinach::DSL
# Create a project without caring about what it's called
- And "I own a project" do
+ step "I own a project" do
@project = create(:project, namespace: @user.namespace)
@project.team << [@user, :master]
end
# Create a specific project called "Shop"
- And 'I own project "Shop"' do
+ step 'I own project "Shop"' do
@project = Project.find_by(name: "Shop")
@project ||= create(:project, name: "Shop", namespace: @user.namespace, snippets_enabled: true)
@project.team << [@user, :master]
end
+ # Add another user to project "Shop"
+ step 'I add a user to project "Shop"' do
+ @project = Project.find_by(name: "Shop")
+ other_user = create(:user, name: 'Alpha')
+ @project.team << [other_user, :master]
+ end
+
# Create another specific project called "Forum"
- And 'I own project "Forum"' do
+ step 'I own project "Forum"' do
@project = Project.find_by(name: "Forum")
@project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project')
@project.team << [@user, :master]
end
# Create an empty project without caring about the name
- And 'I own an empty project' do
+ step 'I own an empty project' do
@project = create(:empty_project,
name: 'Empty Project', namespace: @user.namespace)
@project.team << [@user, :master]
end
- And 'project "Shop" has push event' do
+ step 'I visit my empty project page' do
+ project = Project.find_by(name: 'Empty Project')
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ step 'project "Shop" has push event' do
@project = Project.find_by(name: "Shop")
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "6d394385cf567f80a8fd85055db1ab4c5295806f",
ref: "refs/heads/fix",
user_id: @user.id,
@@ -54,13 +66,13 @@ module SharedProject
)
end
- Then 'I should see project "Shop" activity feed' do
+ step 'I should see project "Shop" activity feed' do
project = Project.find_by(name: "Shop")
page.should have_content "#{@user.name} pushed new branch fix at #{project.name_with_namespace}"
end
- Then 'I should see project settings' do
- current_path.should == edit_project_path(@project)
+ step 'I should see project settings' do
+ current_path.should == edit_namespace_project_path(@project.namespace, @project)
page.should have_content("Project name")
page.should have_content("Features:")
end
@@ -131,7 +143,7 @@ module SharedProject
end
step 'public empty project "Empty Public Project"' do
- create :empty_project, :public, name: "Empty Public Project"
+ create :project_empty_repo, :public, name: "Empty Public Project"
end
step 'project "Community" has comments' do
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
new file mode 100644
index 0000000000..c5aed19331
--- /dev/null
+++ b/features/steps/shared/project_tab.rb
@@ -0,0 +1,48 @@
+require_relative 'active_tab'
+
+module SharedProjectTab
+ include Spinach::DSL
+ include SharedActiveTab
+
+ step 'the active main tab should be Home' do
+ ensure_active_main_tab('Project')
+ end
+
+ step 'the active main tab should be Files' do
+ ensure_active_main_tab('Files')
+ end
+
+ step 'the active main tab should be Commits' do
+ ensure_active_main_tab('Commits')
+ end
+
+ step 'the active main tab should be Network' do
+ ensure_active_main_tab('Network')
+ end
+
+ step 'the active main tab should be Graphs' do
+ ensure_active_main_tab('Graphs')
+ end
+
+ step 'the active main tab should be Issues' do
+ ensure_active_main_tab('Issues')
+ end
+
+ step 'the active main tab should be Merge Requests' do
+ ensure_active_main_tab('Merge Requests')
+ end
+
+ step 'the active main tab should be Snippets' do
+ ensure_active_main_tab('Snippets')
+ end
+
+ step 'the active main tab should be Wiki' do
+ ensure_active_main_tab('Wiki')
+ end
+
+ step 'the active main tab should be Settings' do
+ within '.nav-sidebar' do
+ page.should have_content('Back to project')
+ end
+ end
+end
diff --git a/features/steps/shared/search.rb b/features/steps/shared/search.rb
new file mode 100644
index 0000000000..6c3d601763
--- /dev/null
+++ b/features/steps/shared/search.rb
@@ -0,0 +1,11 @@
+module SharedSearch
+ include Spinach::DSL
+
+ def search_snippet_contents(query)
+ visit "/search?search=#{URI::encode(query)}&snippets=true&scope=snippet_blobs"
+ end
+
+ def search_snippet_titles(query)
+ visit "/search?search=#{URI::encode(query)}&snippets=true&scope=snippet_titles"
+ end
+end
diff --git a/features/steps/shared/shortcuts.rb b/features/steps/shared/shortcuts.rb
new file mode 100644
index 0000000000..bbb7afec0a
--- /dev/null
+++ b/features/steps/shared/shortcuts.rb
@@ -0,0 +1,18 @@
+module SharedActiveTab
+ include Spinach::DSL
+
+ step 'I press "g" and "p"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('p')
+ end
+
+ step 'I press "g" and "i"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('i')
+ end
+
+ step 'I press "g" and "m"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('m')
+ end
+end
diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb
index 543e43196a..bb596c1620 100644
--- a/features/steps/shared/snippet.rb
+++ b/features/steps/shared/snippet.rb
@@ -1,21 +1,63 @@
module SharedSnippet
include Spinach::DSL
- And 'I have public "Personal snippet one" snippet' do
+ step 'I have public "Personal snippet one" snippet' do
create(:personal_snippet,
title: "Personal snippet one",
content: "Test content",
file_name: "snippet.rb",
- private: false,
+ visibility_level: Snippet::PUBLIC,
author: current_user)
end
- And 'I have private "Personal snippet private" snippet' do
+ step 'I have private "Personal snippet private" snippet' do
create(:personal_snippet,
title: "Personal snippet private",
content: "Provate content",
file_name: "private_snippet.rb",
- private: true,
+ visibility_level: Snippet::PRIVATE,
author: current_user)
end
+
+ step 'I have internal "Personal snippet internal" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet internal",
+ content: "Provate content",
+ file_name: "internal_snippet.rb",
+ visibility_level: Snippet::INTERNAL,
+ author: current_user)
+ end
+
+ step 'I have a public many lined snippet' do
+ create(:personal_snippet,
+ title: 'Many lined snippet',
+ content: <<-END.gsub(/^\s+\|/, ''),
+ |line one
+ |line two
+ |line three
+ |line four
+ |line five
+ |line six
+ |line seven
+ |line eight
+ |line nine
+ |line ten
+ |line eleven
+ |line twelve
+ |line thirteen
+ |line fourteen
+ END
+ file_name: 'many_lined_snippet.rb',
+ visibility_level: Snippet::PUBLIC,
+ author: current_user)
+ end
+
+ step 'There is public "Personal snippet one" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet one",
+ content: "Test content",
+ file_name: "snippet.rb",
+ visibility_level: Snippet::PUBLIC,
+ author: create(:user))
+ end
end
diff --git a/features/steps/snippet_search.rb b/features/steps/snippet_search.rb
new file mode 100644
index 0000000000..669c7186c1
--- /dev/null
+++ b/features/steps/snippet_search.rb
@@ -0,0 +1,56 @@
+class Spinach::Features::SnippetSearch < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+ include SharedUser
+ include SharedSearch
+
+ step 'I search for "snippet" in snippet titles' do
+ search_snippet_titles 'snippet'
+ end
+
+ step 'I search for "snippet private" in snippet titles' do
+ search_snippet_titles 'snippet private'
+ end
+
+ step 'I search for "line seven" in snippet contents' do
+ search_snippet_contents 'line seven'
+ end
+
+ step 'I should see "line seven" in results' do
+ page.should have_content 'line seven'
+ end
+
+ step 'I should see "line four" in results' do
+ page.should have_content 'line four'
+ end
+
+ step 'I should see "line ten" in results' do
+ page.should have_content 'line ten'
+ end
+
+ step 'I should not see "line eleven" in results' do
+ page.should_not have_content 'line eleven'
+ end
+
+ step 'I should not see "line three" in results' do
+ page.should_not have_content 'line three'
+ end
+
+ step 'I should see "Personal snippet one" in results' do
+ page.should have_content 'Personal snippet one'
+ end
+
+ step 'I should see "Personal snippet private" in results' do
+ page.should have_content 'Personal snippet private'
+ end
+
+ step 'I should not see "Personal snippet one" in results' do
+ page.should_not have_content 'Personal snippet one'
+ end
+
+ step 'I should not see "Personal snippet private" in results' do
+ page.should_not have_content 'Personal snippet private'
+ end
+
+end
diff --git a/features/steps/snippets/discover.rb b/features/steps/snippets/discover.rb
index 0933793700..2667c1e3d4 100644
--- a/features/steps/snippets/discover.rb
+++ b/features/steps/snippets/discover.rb
@@ -1,13 +1,17 @@
-class DiscoverSnippets < Spinach::FeatureSteps
+class Spinach::Features::SnippetsDiscover < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedSnippet
- Then 'I should see "Personal snippet one" in snippets' do
+ step 'I should see "Personal snippet one" in snippets' do
page.should have_content "Personal snippet one"
end
- And 'I should not see "Personal snippet private" in snippets' do
+ step 'I should see "Personal snippet internal" in snippets' do
+ page.should have_content "Personal snippet internal"
+ end
+
+ step 'I should not see "Personal snippet private" in snippets' do
page.should_not have_content "Personal snippet private"
end
diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb
new file mode 100644
index 0000000000..67669dc0a6
--- /dev/null
+++ b/features/steps/snippets/public_snippets.rb
@@ -0,0 +1,25 @@
+class Spinach::Features::PublicSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+
+ step 'I should see snippet "Personal snippet one"' do
+ page.should have_no_xpath("//i[@class='public-snippet']")
+ end
+
+ step 'I should see raw snippet "Personal snippet one"' do
+ page.should have_text(snippet.content)
+ end
+
+ step 'I visit snippet page "Personal snippet one"' do
+ visit snippet_path(snippet)
+ end
+
+ step 'I visit snippet raw page "Personal snippet one"' do
+ visit raw_snippet_path(snippet)
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
+ end
+end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index 040b5390a5..de936db85e 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -1,28 +1,28 @@
-class SnippetsFeature < Spinach::FeatureSteps
+class Spinach::Features::Snippets < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
include SharedSnippet
- Given 'I click link "Personal snippet one"' do
+ step 'I click link "Personal snippet one"' do
click_link "Personal snippet one"
end
- And 'I should not see "Personal snippet one" in snippets' do
+ step 'I should not see "Personal snippet one" in snippets' do
page.should_not have_content "Personal snippet one"
end
- And 'I click link "Edit"' do
+ step 'I click link "Edit"' do
within ".file-title" do
click_link "Edit"
end
end
- And 'I click link "Destroy"' do
+ step 'I click link "Destroy"' do
click_link "remove"
end
- And 'I submit new snippet "Personal snippet three"' do
+ step 'I submit new snippet "Personal snippet three"' do
fill_in "personal_snippet_title", :with => "Personal snippet three"
fill_in "personal_snippet_file_name", :with => "my_snippet.rb"
within('.file-editor') do
@@ -31,30 +31,30 @@ class SnippetsFeature < Spinach::FeatureSteps
click_button "Create snippet"
end
- Then 'I should see snippet "Personal snippet three"' do
+ step 'I should see snippet "Personal snippet three"' do
page.should have_content "Personal snippet three"
page.should have_content "Content of snippet three"
end
- And 'I submit new title "Personal snippet new title"' do
+ step 'I submit new title "Personal snippet new title"' do
fill_in "personal_snippet_title", :with => "Personal snippet new title"
click_button "Save"
end
- Then 'I should see "Personal snippet new title"' do
+ step 'I should see "Personal snippet new title"' do
page.should have_content "Personal snippet new title"
end
- And 'I uncheck "Private" checkbox' do
- choose "Public"
+ step 'I uncheck "Private" checkbox' do
+ choose "Internal"
click_button "Save"
end
- Then 'I should see "Personal snippet one" public' do
+ step 'I should see "Personal snippet one" public' do
page.should have_no_xpath("//i[@class='public-snippet']")
end
- And 'I visit snippet page "Personal snippet one"' do
+ step 'I visit snippet page "Personal snippet one"' do
visit snippet_path(snippet)
end
diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb
index 2d7ffc866e..866f637ab6 100644
--- a/features/steps/snippets/user.rb
+++ b/features/steps/snippets/user.rb
@@ -1,40 +1,54 @@
-class UserSnippets < Spinach::FeatureSteps
+class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedSnippet
- Given 'I visit my snippets page' do
+ step 'I visit my snippets page' do
visit user_snippets_path(current_user)
end
- Then 'I should see "Personal snippet one" in snippets' do
+ step 'I should see "Personal snippet one" in snippets' do
page.should have_content "Personal snippet one"
end
- And 'I should see "Personal snippet private" in snippets' do
+ step 'I should see "Personal snippet private" in snippets' do
page.should have_content "Personal snippet private"
end
- Then 'I should not see "Personal snippet one" in snippets' do
+ step 'I should see "Personal snippet internal" in snippets' do
+ page.should have_content "Personal snippet internal"
+ end
+
+ step 'I should not see "Personal snippet one" in snippets' do
page.should_not have_content "Personal snippet one"
end
- And 'I should not see "Personal snippet private" in snippets' do
+ step 'I should not see "Personal snippet private" in snippets' do
page.should_not have_content "Personal snippet private"
end
- Given 'I click "Public" filter' do
+ step 'I should not see "Personal snippet internal" in snippets' do
+ page.should_not have_content "Personal snippet internal"
+ end
+
+ step 'I click "Internal" filter' do
within('.nav-stacked') do
- click_link "Public"
+ click_link "Internal"
end
end
- Given 'I click "Private" filter' do
+ step 'I click "Private" filter' do
within('.nav-stacked') do
click_link "Private"
end
end
+ step 'I click "Public" filter' do
+ within('.nav-stacked') do
+ click_link "Public"
+ end
+ end
+
def snippet
@snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end
diff --git a/features/steps/user.rb b/features/steps/user.rb
index 5fb248ffcb..10cae692a8 100644
--- a/features/steps/user.rb
+++ b/features/steps/user.rb
@@ -5,6 +5,39 @@ class Spinach::Features::User < Spinach::FeatureSteps
include SharedProject
step 'I should see user "John Doe" page' do
- expect(page.title).to match(/^\s*John Doe/)
+ expect(title).to match(/^\s*John Doe/)
+ end
+
+ step '"John Doe" has contributions' do
+ user = User.find_by(name: 'John Doe')
+ project = contributed_project
+
+ # Issue controbution
+ issue_params = { title: 'Bug in old browser' }
+ Issues::CreateService.new(project, user, issue_params).execute
+
+ # Push code contribution
+ push_params = {
+ project: project,
+ action: Event::PUSHED,
+ author_id: user.id,
+ data: { commit_count: 3 }
+ }
+
+ Event.create(push_params)
+ end
+
+ step 'I should see contributed projects' do
+ within '.contributed-projects' do
+ page.should have_content(@contributed_project.name)
+ end
+ end
+
+ step 'I should see contributions calendar' do
+ page.should have_css('.cal-heatmap-container')
+ end
+
+ def contributed_project
+ @contributed_project ||= create(:project, :public)
end
end
diff --git a/features/support/env.rb b/features/support/env.rb
index 22f28987fe..be17065ccf 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,20 +1,20 @@
-require 'simplecov' unless ENV['CI']
+if ENV['SIMPLECOV']
+ require 'simplecov'
+end
-if ENV['TRAVIS']
+if ENV['COVERALLS']
require 'coveralls'
- Coveralls.wear!
+ Coveralls.wear_merged!
end
ENV['RAILS_ENV'] = 'test'
require './config/environment'
-
require 'rspec'
require 'rspec/expectations'
require 'database_cleaner'
require 'spinach/capybara'
require 'sidekiq/testing/inline'
-
%w(select2_helper test_env repo_helpers).each do |f|
require Rails.root.join('spec', 'support', f)
end
@@ -47,8 +47,8 @@ Spinach.hooks.after_scenario do
end
Spinach.hooks.before_run do
+ include RSpec::Mocks::ExampleMethods
TestEnv.init(mailer: false)
- RSpec::Mocks::setup self
include FactoryGirl::Syntax::Methods
end
diff --git a/features/user.feature b/features/user.feature
index a2167935fd..69618e929c 100644
--- a/features/user.feature
+++ b/features/user.feature
@@ -67,3 +67,12 @@ Feature: User
And I should see project "Enterprise"
And I should not see project "Internal"
And I should not see project "Community"
+
+ @javascript
+ Scenario: "John Doe" contribution profile
+ Given I sign in as a user
+ And "John Doe" has contributions
+ When I visit user "John Doe" page
+ Then I should see user "John Doe" page
+ And I should see contributed projects
+ And I should see contributions calendar
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 2c7cd9038c..60858a3940 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -2,10 +2,11 @@ Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
module API
class API < Grape::API
+ include APIGuard
version 'v3', using: :path
rescue_from ActiveRecord::RecordNotFound do
- rack_response({'message' => '404 Not found'}.to_json, 404)
+ rack_response({ 'message' => '404 Not found' }.to_json, 404)
end
rescue_from :all do |exception|
@@ -18,7 +19,7 @@ module API
message << " " << trace.join("\n ")
API.logger.add Logger::FATAL, message
- rack_response({'message' => '500 Internal Server Error'}, 500)
+ rack_response({ 'message' => '500 Internal Server Error' }, 500)
end
format :json
@@ -27,6 +28,7 @@ module API
helpers APIHelpers
mount Groups
+ mount GroupMembers
mount Users
mount Projects
mount Repositories
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
new file mode 100644
index 0000000000..b9994fcefd
--- /dev/null
+++ b/lib/api/api_guard.rb
@@ -0,0 +1,172 @@
+# Guard API with OAuth 2.0 Access Token
+
+require 'rack/oauth2'
+
+module APIGuard
+ extend ActiveSupport::Concern
+
+ included do |base|
+ # OAuth2 Resource Server Authentication
+ use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
+ # The authenticator only fetches the raw token string
+
+ # Must yield access token to store it in the env
+ request.access_token
+ end
+
+ helpers HelperMethods
+
+ install_error_responders(base)
+ end
+
+ # Helper Methods for Grape Endpoint
+ module HelperMethods
+ # Invokes the doorkeeper guard.
+ #
+ # If token is presented and valid, then it sets @current_user.
+ #
+ # If the token does not have sufficient scopes to cover the requred scopes,
+ # then it raises InsufficientScopeError.
+ #
+ # If the token is expired, then it raises ExpiredError.
+ #
+ # If the token is revoked, then it raises RevokedError.
+ #
+ # If the token is not found (nil), then it raises TokenNotFoundError.
+ #
+ # Arguments:
+ #
+ # scopes: (optional) scopes required for this guard.
+ # Defaults to empty array.
+ #
+ def doorkeeper_guard!(scopes: [])
+ if (access_token = find_access_token).nil?
+ raise TokenNotFoundError
+
+ else
+ case validate_access_token(access_token, scopes)
+ when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+ when Oauth2::AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+ when Oauth2::AccessTokenValidationService::REVOKED
+ raise RevokedError
+ when Oauth2::AccessTokenValidationService::VALID
+ @current_user = User.find(access_token.resource_owner_id)
+ end
+ end
+ end
+
+ def doorkeeper_guard(scopes: [])
+ if access_token = find_access_token
+ case validate_access_token(access_token, scopes)
+ when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+
+ when Oauth2::AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+
+ when Oauth2::AccessTokenValidationService::REVOKED
+ raise RevokedError
+
+ when Oauth2::AccessTokenValidationService::VALID
+ @current_user = User.find(access_token.resource_owner_id)
+ end
+ end
+ end
+
+ def current_user
+ @current_user
+ end
+
+ private
+ def find_access_token
+ @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods)
+ end
+
+ def doorkeeper_request
+ @doorkeeper_request ||= ActionDispatch::Request.new(env)
+ end
+
+ def validate_access_token(access_token, scopes)
+ Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes)
+ end
+ end
+
+ module ClassMethods
+ # Installs the doorkeeper guard on the whole Grape API endpoint.
+ #
+ # Arguments:
+ #
+ # scopes: (optional) scopes required for this guard.
+ # Defaults to empty array.
+ #
+ def guard_all!(scopes: [])
+ before do
+ guard! scopes: scopes
+ end
+ end
+
+ private
+ def install_error_responders(base)
+ error_classes = [ MissingTokenError, TokenNotFoundError,
+ ExpiredError, RevokedError, InsufficientScopeError]
+
+ base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
+ end
+
+ def oauth2_bearer_token_error_handler
+ Proc.new do |e|
+ response =
+ case e
+ when MissingTokenError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
+
+ when TokenNotFoundError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Bad Access Token.")
+
+ when ExpiredError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token is expired. You can either do re-authorization or token refresh.")
+
+ when RevokedError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token was revoked. You have to re-authorize from the user.")
+
+ when InsufficientScopeError
+ # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
+ # does not include WWW-Authenticate header, which breaks the standard.
+ Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
+ :insufficient_scope,
+ Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope],
+ { scope: e.scopes })
+ end
+
+ response.finish
+ end
+ end
+ end
+
+ #
+ # Exceptions
+ #
+
+ class MissingTokenError < StandardError; end
+
+ class TokenNotFoundError < StandardError; end
+
+ class ExpiredError < StandardError; end
+
+ class RevokedError < StandardError; end
+
+ class InsufficientScopeError < StandardError
+ attr_reader :scopes
+ def initialize(scopes)
+ @scopes = scopes
+ end
+ end
+end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index b32a4aa7bc..592100a704 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -14,7 +14,8 @@ module API
# Example Request:
# GET /projects/:id/repository/branches
get ":id/repository/branches" do
- present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
+ branches = user_project.repository.branches.sort_by(&:name)
+ present branches, with: Entities::RepoObject, project: user_project
end
# Get a single branch
@@ -25,8 +26,8 @@ module API
# Example Request:
# GET /projects/:id/repository/branches/:branch
get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- not_found!("Branch does not exist") if @branch.nil?
+ @branch = user_project.repository.branches.find { |item| item.name == params[:branch] }
+ not_found!("Branch") unless @branch
present @branch, with: Entities::RepoObject, project: user_project
end
@@ -43,7 +44,7 @@ module API
authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch])
- not_found! unless @branch
+ not_found!("Branch") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
user_project.protected_branches.create(name: @branch.name) unless protected_branch
@@ -63,7 +64,7 @@ module API
authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch])
- not_found! unless @branch
+ not_found!("Branch does not exist") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch
@@ -80,9 +81,16 @@ module API
# POST /projects/:id/repository/branches
post ":id/repository/branches" do
authorize_push_project
- @branch = CreateBranchService.new.execute(user_project, params[:branch_name], params[:ref], current_user)
+ result = CreateBranchService.new(user_project, current_user).
+ execute(params[:branch_name], params[:ref])
- present @branch, with: Entities::RepoObject, project: user_project
+ if result[:status] == :success
+ present result[:branch],
+ with: Entities::RepoObject,
+ project: user_project
+ else
+ render_api_error!(result[:message], 400)
+ end
end
# Delete branch
@@ -92,14 +100,18 @@ module API
# branch (required) - The name of the branch
# Example Request:
# DELETE /projects/:id/repository/branches/:branch
- delete ":id/repository/branches/:branch" do
+ delete ":id/repository/branches/:branch",
+ requirements: { branch: /.*/ } do
authorize_push_project
- result = DeleteBranchService.new.execute(user_project, params[:branch], current_user)
+ result = DeleteBranchService.new(user_project, current_user).
+ execute(params[:branch])
- if result[:state] == :success
- true
+ if result[:status] == :success
+ {
+ branch_name: params[:branch]
+ }
else
- render_api_error!(result[:message], 405)
+ render_api_error!(result[:message], result[:return_code])
end
end
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 4a67313430..0de4e720ff 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -50,6 +50,67 @@ module API
not_found! "Commit" unless commit
commit.diffs
end
+
+ # Get a commit's comments
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash
+ # Examples:
+ # GET /projects/:id/repository/commits/:sha/comments
+ get ':id/repository/commits/:sha/comments' do
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! 'Commit' unless commit
+ notes = Note.where(commit_id: commit.id)
+ present paginate(notes), with: Entities::CommitNote
+ end
+
+ # Post comment to commit
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash
+ # note (required) - Text of comment
+ # path (optional) - The file path
+ # line (optional) - The line number
+ # line_type (optional) - The type of line (new or old)
+ # Examples:
+ # POST /projects/:id/repository/commits/:sha/comments
+ post ':id/repository/commits/:sha/comments' do
+ required_attributes! [:note]
+
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! 'Commit' unless commit
+ opts = {
+ note: params[:note],
+ noteable_type: 'Commit',
+ commit_id: commit.id
+ }
+
+ if params[:path] && params[:line] && params[:line_type]
+ commit.diffs.each do |diff|
+ next unless diff.new_path == params[:path]
+ lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a)
+
+ lines.each do |line|
+ next unless line.new_pos == params[:line].to_i && line.type == params[:line_type]
+ break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+ end
+
+ break if opts[:line_code]
+ end
+ end
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+
+ if note.save
+ present note, with: Entities::CommitNote
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
end
end
end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 7f5a125038..06eb775684 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -58,7 +58,7 @@ module API
if key.valid? && user_project.deploy_keys << key
present key, with: Entities::SSHKey
else
- not_found!
+ render_validation_error!(key)
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 74fdef9354..36332bc651 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -14,9 +14,14 @@ module API
expose :bio, :skype, :linkedin, :twitter, :website_url
end
+ class Identity < Grape::Entity
+ expose :provider, :extern_uid
+ end
+
class UserFull < User
expose :email
- expose :theme_id, :color_scheme_id, :extern_uid, :provider
+ expose :theme_id, :color_scheme_id, :projects_limit
+ expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
end
@@ -30,7 +35,8 @@ module API
end
class ProjectHook < Hook
- expose :project_id, :push_events, :issues_events, :merge_requests_events
+ expose :project_id, :push_events
+ expose :issues_events, :merge_requests_events, :tag_push_events
end
class ForkedFromProject < Grape::Entity
@@ -40,7 +46,7 @@ module API
end
class Project < Grape::Entity
- expose :id, :description, :default_branch
+ expose :id, :description, :default_branch, :tag_list
expose :public?, as: :public
expose :archived?, as: :archived
expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
@@ -48,18 +54,20 @@ module API
expose :name, :name_with_namespace
expose :path, :path_with_namespace
expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at
+ expose :creator_id
expose :namespace
- expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
+ expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
+ expose :avatar_url
end
class ProjectMember < UserBasic
- expose :project_access, as: :access_level do |user, options|
- options[:project].users_projects.find_by(user_id: user.id).project_access
+ expose :access_level do |user, options|
+ options[:project].project_members.find_by(user_id: user.id).access_level
end
end
class Group < Grape::Entity
- expose :id, :name, :path, :owner_id
+ expose :id, :name, :path, :description
end
class GroupDetail < Group
@@ -67,8 +75,27 @@ module API
end
class GroupMember < UserBasic
- expose :group_access, as: :access_level do |user, options|
- options[:group].users_groups.find_by(user_id: user.id).group_access
+ expose :access_level do |user, options|
+ options[:group].group_members.find_by(user_id: user.id).access_level
+ end
+ end
+
+ class RepoTag < Grape::Entity
+ expose :name
+ expose :message do |repo_obj, _options|
+ if repo_obj.respond_to?(:message)
+ repo_obj.message
+ else
+ nil
+ end
+ end
+
+ expose :commit do |repo_obj, options|
+ if repo_obj.respond_to?(:commit)
+ repo_obj.commit
+ elsif options[:project]
+ options[:project].repository.commit(repo_obj.target)
+ end
end
end
@@ -117,11 +144,16 @@ module API
class ProjectEntity < Grape::Entity
expose :id, :iid
- expose (:project_id) { |entity| entity.project.id }
+ expose(:project_id) { |entity| entity.project.id }
expose :title, :description
expose :state, :created_at, :updated_at
end
+ class RepoDiff < Grape::Entity
+ expose :old_path, :new_path, :a_mode, :b_mode, :diff
+ expose :new_file, :renamed_file, :deleted_file
+ end
+
class Milestone < ProjectEntity
expose :due_date
end
@@ -141,6 +173,12 @@ module API
expose :milestone, using: Entities::Milestone
end
+ class MergeRequestChanges < MergeRequest
+ expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
+ compare.diffs
+ end
+ end
+
class SSHKey < Grape::Entity
expose :id, :title, :key, :created_at
end
@@ -158,11 +196,25 @@ module API
expose :author, using: Entities::UserBasic
end
+ class CommitNote < Grape::Entity
+ expose :note
+ expose(:path) { |note| note.diff_file_name }
+ expose(:line) { |note| note.diff_new_line }
+ expose(:line_type) { |note| note.diff_line_type }
+ expose :author, using: Entities::UserBasic
+ end
+
class Event < Grape::Entity
expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id
expose :data, :target_title
expose :created_at
+
+ expose :author_username do |event, options|
+ if event.author
+ event.author.username
+ end
+ end
end
class Namespace < Grape::Entity
@@ -170,24 +222,24 @@ module API
end
class ProjectAccess < Grape::Entity
- expose :project_access, as: :access_level
+ expose :access_level
expose :notification_level
end
class GroupAccess < Grape::Entity
- expose :group_access, as: :access_level
+ expose :access_level
expose :notification_level
end
class ProjectWithAccess < Project
expose :permissions do
expose :project_access, using: Entities::ProjectAccess do |project, options|
- project.users_projects.find_by(user_id: options[:user].id)
+ project.project_members.find_by(user_id: options[:user].id)
end
expose :group_access, using: Entities::GroupAccess do |project, options|
if project.group
- project.group.users_groups.find_by(user_id: options[:user].id)
+ project.group.group_members.find_by(user_id: options[:user].id)
end
end
end
@@ -197,11 +249,6 @@ module API
expose :name, :color
end
- class RepoDiff < Grape::Entity
- expose :old_path, :new_path, :a_mode, :b_mode, :diff
- expose :new_file, :renamed_file, :deleted_file
- end
-
class Compare < Grape::Entity
expose :commit, using: Entities::RepoCommit do |compare, options|
Commit.decorate(compare.commits).last
@@ -225,5 +272,9 @@ module API
class Contributor < Grape::Entity
expose :name, :email, :commits, :additions, :deletions
end
+
+ class BroadcastMessage < Grape::Entity
+ expose :message, :starts_at, :ends_at, :color, :font
+ end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index e63e635a4d..3176ef0e25 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -35,7 +35,7 @@ module API
file_path = attrs.delete(:file_path)
commit = user_project.repository.commit(ref)
- not_found! "Commit" unless commit
+ not_found! 'Commit' unless commit
blob = user_project.repository.blob_at(commit.sha, file_path)
@@ -53,7 +53,7 @@ module API
commit_id: commit.id,
}
else
- render_api_error!('File not found', 404)
+ not_found! 'File'
end
end
@@ -85,7 +85,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
@@ -117,7 +117,8 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ http_status = result[:http_status] || 400
+ render_api_error!(result[:message], http_status)
end
end
@@ -149,7 +150,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
end
diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb
new file mode 100644
index 0000000000..ab9b7c602b
--- /dev/null
+++ b/lib/api/group_members.rb
@@ -0,0 +1,87 @@
+module API
+ class GroupMembers < Grape::API
+ before { authenticate! }
+
+ resource :groups do
+ # Get a list of group members viewable by the authenticated user.
+ #
+ # Example Request:
+ # GET /groups/:id/members
+ get ":id/members" do
+ group = find_group(params[:id])
+ users = group.users
+ present users, with: Entities::GroupMember, group: group
+ end
+
+ # Add a user to the list of group members
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /groups/:id/members
+ post ":id/members" do
+ group = find_group(params[:id])
+ authorize! :admin_group, group
+ required_attributes! [:user_id, :access_level]
+
+ unless validate_access_level?(params[:access_level])
+ render_api_error!("Wrong access level", 422)
+ end
+
+ if group.group_members.find_by(user_id: params[:user_id])
+ render_api_error!("Already exists", 409)
+ end
+
+ group.add_users([params[:user_id]], params[:access_level], current_user)
+ member = group.group_members.find_by(user_id: params[:user_id])
+ present member.user, with: Entities::GroupMember, group: group
+ end
+
+ # Update group member
+ #
+ # Parameters:
+ # id (required) - The ID of a group
+ # user_id (required) - The ID of a group member
+ # access_level (required) - Project access level
+ # Example Request:
+ # PUT /groups/:id/members/:user_id
+ put ':id/members/:user_id' do
+ group = find_group(params[:id])
+ authorize! :admin_group, group
+ required_attributes! [:access_level]
+
+ group_member = group.group_members.find_by(user_id: params[:user_id])
+ not_found!('User can not be found') if group_member.nil?
+
+ if group_member.update_attributes(access_level: params[:access_level])
+ @member = group_member.user
+ present @member, with: Entities::GroupMember, group: group
+ else
+ handle_member_errors group_member.errors
+ end
+ end
+
+ # Remove member.
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ #
+ # Example Request:
+ # DELETE /groups/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ group = find_group(params[:id])
+ authorize! :admin_group, group
+ member = group.group_members.find_by(user_id: params[:user_id])
+
+ if member.nil?
+ render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
+ else
+ member.destroy
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index caa2ca97a3..8cb9f92097 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -4,32 +4,19 @@ module API
before { authenticate! }
resource :groups do
- helpers do
- def find_group(id)
- group = Group.find(id)
-
- if can?(current_user, :read_group, group)
- group
- else
- render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
- end
- end
-
- def validate_access_level?(level)
- Gitlab::Access.options_with_owner.values.include? level.to_i
- end
- end
-
# Get a groups list
#
# Example Request:
# GET /groups
get do
- if current_user.admin
- @groups = paginate Group
- else
- @groups = paginate current_user.groups
- end
+ @groups = if current_user.admin
+ Group.all
+ else
+ current_user.groups
+ end
+
+ @groups = @groups.search(params[:search]) if params[:search].present?
+ @groups = paginate @groups
present @groups, with: Entities::Group
end
@@ -44,14 +31,14 @@ module API
authenticated_as_admin!
required_attributes! [:name, :path]
- attrs = attributes_for_keys [:name, :path]
+ attrs = attributes_for_keys [:name, :path, :description]
@group = Group.new(attrs)
- @group.owner = current_user
if @group.save
+ @group.add_owner(current_user)
present @group, with: Entities::Group
else
- not_found!
+ render_api_error!("Failed to save group #{@group.errors.messages}", 400)
end
end
@@ -74,7 +61,7 @@ module API
# DELETE /groups/:id
delete ":id" do
group = find_group(params[:id])
- authorize! :manage_group, group
+ authorize! :admin_group, group
group.destroy
end
@@ -94,58 +81,7 @@ module API
if result
present group
else
- not_found!
- end
- end
-
- # Get a list of group members viewable by the authenticated user.
- #
- # Example Request:
- # GET /groups/:id/members
- get ":id/members" do
- group = find_group(params[:id])
- members = group.users_groups
- users = (paginate members).collect(&:user)
- present users, with: Entities::GroupMember, group: group
- end
-
- # Add a user to the list of group members
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- # access_level (required) - Project access level
- # Example Request:
- # POST /groups/:id/members
- post ":id/members" do
- required_attributes! [:user_id, :access_level]
- unless validate_access_level?(params[:access_level])
- render_api_error!("Wrong access level", 422)
- end
- group = find_group(params[:id])
- if group.users_groups.find_by(user_id: params[:user_id])
- render_api_error!("Already exists", 409)
- end
- group.add_users([params[:user_id]], params[:access_level])
- member = group.users_groups.find_by(user_id: params[:user_id])
- present member.user, with: Entities::GroupMember, group: group
- end
-
- # Remove member.
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- #
- # Example Request:
- # DELETE /groups/:id/members/:user_id
- delete ":id/members/:user_id" do
- group = find_group(params[:id])
- member = group.users_groups.find_by(user_id: params[:user_id])
- if member.nil?
- render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
- else
- member.destroy
+ render_api_error!("Failed to transfer project #{project.errors.messages}", 400)
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6af0f6d1b2..be133a2920 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -11,7 +11,7 @@ module API
def current_user
private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
- @current_user ||= User.find_by(authentication_token: private_token)
+ @current_user ||= (User.find_by(authentication_token: private_token) || doorkeeper_guard)
unless @current_user && Gitlab::UserAccess.allowed?(@current_user)
return nil
@@ -20,7 +20,7 @@ module API
identifier = sudo_identifier()
# If the sudo is the current user do nothing
- if (identifier && !(@current_user.id == identifier || @current_user.username == identifier))
+ if identifier && !(@current_user.id == identifier || @current_user.username == identifier)
render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
@current_user = User.by_username_or_id(identifier)
not_found!("No user id or username for: #{identifier}") if @current_user.nil?
@@ -33,7 +33,7 @@ module API
identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
# Regex for integers
- if (!!(identifier =~ /^[0-9]+$/))
+ if !!(identifier =~ /^[0-9]+$/)
identifier.to_i
else
identifier
@@ -42,7 +42,7 @@ module API
def user_project
@project ||= find_project(params[:id])
- @project || not_found!
+ @project || not_found!("Project")
end
def find_project(id)
@@ -55,6 +55,21 @@ module API
end
end
+ def find_group(id)
+ begin
+ group = Group.find(id)
+ rescue ActiveRecord::RecordNotFound
+ group = Group.find_by!(path: id)
+ end
+
+ if can?(current_user, :read_group, group)
+ group
+ else
+ forbidden!("#{current_user.username} lacks sufficient "\
+ "access to #{group.name}")
+ end
+ end
+
def paginate(relation)
per_page = params[:per_page].to_i
paginated = relation.page(params[:page]).per(per_page)
@@ -67,11 +82,18 @@ module API
unauthorized! unless current_user
end
+ def authenticate_by_gitlab_shell_token!
+ input = params['secret_token'].try(:chomp)
+ unless Devise.secure_compare(secret_token, input)
+ unauthorized!
+ end
+ end
+
def authenticated_as_admin!
forbidden! unless current_user.is_admin?
end
- def authorize! action, subject
+ def authorize!(action, subject)
unless abilities.allowed?(current_user, action, subject)
forbidden!
end
@@ -131,10 +153,32 @@ module API
errors
end
+ def validate_access_level?(level)
+ Gitlab::Access.options_with_owner.values.include? level.to_i
+ end
+
+ def issuable_order_by
+ if params["order_by"] == 'updated_at'
+ 'updated_at'
+ else
+ 'created_at'
+ end
+ end
+
+ def issuable_sort
+ if params["sort"] == 'asc'
+ :asc
+ else
+ :desc
+ end
+ end
+
# error helpers
- def forbidden!
- render_api_error!('403 Forbidden', 403)
+ def forbidden!(reason = nil)
+ message = ['403 Forbidden']
+ message << " - #{reason}" if reason
+ render_api_error!(message.join(' '), 403)
end
def bad_request!(attribute)
@@ -155,11 +199,21 @@ module API
end
def not_allowed!
- render_api_error!('Method Not Allowed', 405)
+ render_api_error!('405 Method Not Allowed', 405)
+ end
+
+ def conflict!(message = nil)
+ render_api_error!(message || '409 Conflict', 409)
+ end
+
+ def render_validation_error!(model)
+ if model.errors.any?
+ render_api_error!(model.errors.messages || '400 Bad Request', 400)
+ end
end
def render_api_error!(message, status)
- error!({'message' => message}, status)
+ error!({ 'message' => message }, status)
end
private
@@ -183,5 +237,14 @@ module API
abilities
end
end
+
+ def secret_token
+ File.read(Rails.root.join('.gitlab_shell_secret')).chomp
+ end
+
+ def handle_member_errors(errors)
+ error!(errors[:access_level], 422) if errors[:access_level].any?
+ not_found!(errors)
+ end
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 5850892df0..f98a17773e 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,6 +1,8 @@
module API
# Internal access API
class Internal < Grape::API
+ before { authenticate_by_gitlab_shell_token! }
+
namespace 'internal' do
# Check if git command is allowed to project
#
@@ -12,33 +14,47 @@ module API
# ref - branch name
# forced_push - forced_push
#
- get "/allowed" do
+ post "/allowed" do
+ status 200
+
+ actor =
+ if params[:key_id]
+ Key.find_by(id: params[:key_id])
+ elsif params[:user_id]
+ User.find_by(id: params[:user_id])
+ end
+
+ unless actor
+ return Gitlab::GitAccessStatus.new(false, 'No such user or key')
+ end
+
+ project_path = params[:project]
+
# Check for *.wiki repositories.
# Strip out the .wiki from the pathname before finding the
# project. This applies the correct project permissions to
# the wiki repository as well.
- project_path = params[:project]
- project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/
+ wiki = project_path.end_with?('.wiki')
+ project_path.chomp!('.wiki') if wiki
+
project = Project.find_with_namespace(project_path)
- return false unless project
- actor = if params[:key_id]
- Key.find(params[:key_id])
- elsif params[:user_id]
- User.find(params[:user_id])
- end
+ if project
+ access =
+ if wiki
+ Gitlab::GitAccessWiki.new(actor, project)
+ else
+ Gitlab::GitAccess.new(actor, project)
+ end
- return false unless actor
+ status = access.check(params[:action], params[:changes])
+ end
- Gitlab::GitAccess.new.allowed?(
- actor,
- params[:action],
- project,
- params[:ref],
- params[:oldrev],
- params[:newrev],
- params[:forced_push]
- )
+ if project && access.can_read_project?
+ status
+ else
+ Gitlab::GitAccessStatus.new(false, 'No such project')
+ end
end
#
@@ -56,6 +72,14 @@ module API
gitlab_rev: Gitlab::REVISION,
}
end
+
+ get "/broadcast_message" do
+ if message = BroadcastMessage.current
+ present message, with: Entities::BroadcastMessage
+ else
+ {}
+ end
+ end
end
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index eb6a74cd2b..ff062be604 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -3,13 +3,46 @@ module API
class Issues < Grape::API
before { authenticate! }
+ helpers do
+ def filter_issues_state(issues, state)
+ case state
+ when 'opened' then issues.opened
+ when 'closed' then issues.closed
+ else issues
+ end
+ end
+
+ def filter_issues_labels(issues, labels)
+ issues.includes(:labels).where('labels.title' => labels.split(','))
+ end
+
+ def filter_issues_milestone(issues, milestone)
+ issues.includes(:milestone).where('milestones.title' => milestone)
+ end
+ end
+
resource :issues do
# Get currently authenticated user's issues
#
- # Example Request:
+ # Parameters:
+ # state (optional) - Return "opened" or "closed" issues
+ # labels (optional) - Comma-separated list of label names
+ # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+ # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+ #
+ # Example Requests:
# GET /issues
+ # GET /issues?state=opened
+ # GET /issues?state=closed
+ # GET /issues?labels=foo
+ # GET /issues?labels=foo,bar
+ # GET /issues?labels=foo,bar&state=opened
get do
- present paginate(current_user.issues), with: Entities::Issue
+ issues = current_user.issues
+ 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)
+ present paginate(issues), with: Entities::Issue
end
end
@@ -18,10 +51,32 @@ module API
#
# Parameters:
# id (required) - The ID of a project
- # Example Request:
+ # state (optional) - Return "opened" or "closed" issues
+ # labels (optional) - Comma-separated list of label names
+ # milestone (optional) - Milestone title
+ # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+ # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+ #
+ # Example Requests:
# GET /projects/:id/issues
+ # GET /projects/:id/issues?state=opened
+ # GET /projects/:id/issues?state=closed
+ # GET /projects/:id/issues?labels=foo
+ # GET /projects/:id/issues?labels=foo,bar
+ # GET /projects/:id/issues?labels=foo,bar&state=opened
+ # GET /projects/:id/issues?milestone=1.0.0
+ # GET /projects/:id/issues?milestone=1.0.0&state=closed
get ":id/issues" do
- present paginate(user_project.issues), with: Entities::Issue
+ issues = user_project.issues
+ issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
+ issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
+
+ unless params[:milestone].nil?
+ issues = filter_issues_milestone(issues, params[:milestone])
+ end
+
+ issues.reorder(issuable_order_by => issuable_sort)
+ present paginate(issues), with: Entities::Issue
end
# Get a single project issue
@@ -67,7 +122,7 @@ module API
present issue, with: Entities::Issue
else
- not_found!
+ render_validation_error!(issue)
end
end
@@ -107,7 +162,7 @@ module API
present issue, with: Entities::Issue
else
- not_found!
+ render_validation_error!(issue)
end
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 2fdf53ffec..78ca58ad0d 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -30,16 +30,14 @@ module API
attrs = attributes_for_keys [:name, :color]
label = user_project.find_label(attrs[:name])
- if label
- return render_api_error!('Label already exists', 409)
- end
+ conflict!('Label already exists') if label
label = user_project.labels.create(attrs)
if label.valid?
present label, with: Entities::Label
else
- render_api_error!(label.errors.full_messages.join(', '), 400)
+ render_validation_error!(label)
end
end
@@ -56,9 +54,7 @@ module API
required_attributes! [:name]
label = user_project.find_label(params[:name])
- if !label
- return render_api_error!('Label not found', 404)
- end
+ not_found!('Label') unless label
label.destroy
end
@@ -66,10 +62,11 @@ module API
# Updates an existing label. At least one optional parameter is required.
#
# Parameters:
- # id (required) - The ID of a project
- # name (optional) - The name of the label to be deleted
- # color (optional) - Color of the label given in 6-digit hex
- # notation with leading '#' sign (e.g. #FFAABB)
+ # id (required) - The ID of a project
+ # name (required) - The name of the label to be deleted
+ # new_name (optional) - The new name of the label
+ # color (optional) - Color of the label given in 6-digit hex
+ # notation with leading '#' sign (e.g. #FFAABB)
# Example Request:
# PUT /projects/:id/labels
put ':id/labels' do
@@ -77,16 +74,14 @@ module API
required_attributes! [:name]
label = user_project.find_label(params[:name])
- if !label
- return render_api_error!('Label not found', 404)
- end
+ not_found!('Label not found') unless label
attrs = attributes_for_keys [:new_name, :color]
if attrs.empty?
- return render_api_error!('Required parameters "name" or "color" ' \
- 'missing',
- 400)
+ render_api_error!('Required parameters "new_name" or "color" ' \
+ 'missing',
+ 400)
end
# Rename new name to the actual label attribute name
@@ -95,7 +90,7 @@ module API
if label.update(attrs)
present label, with: Entities::Label
else
- render_api_error!(label.errors.full_messages.join(', '), 400)
+ render_validation_error!(label)
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 8726379bf3..f3765f5ab0 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -10,8 +10,13 @@ module API
error!(errors[:project_access], 422)
elsif errors[:branch_conflict].any?
error!(errors[:branch_conflict], 422)
+ elsif errors[:validate_fork].any?
+ error!(errors[:validate_fork], 422)
+ elsif errors[:validate_branches].any?
+ conflict!(errors[:validate_branches])
end
- not_found!
+
+ render_api_error!(errors, 400)
end
end
@@ -20,23 +25,32 @@ module API
# Parameters:
# id (required) - The ID of a project
# state (optional) - Return requests "merged", "opened" or "closed"
+ # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+ # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
#
# Example:
# GET /projects/:id/merge_requests
# GET /projects/:id/merge_requests?state=opened
# GET /projects/:id/merge_requests?state=closed
+ # GET /projects/:id/merge_requests?order_by=created_at
+ # GET /projects/:id/merge_requests?order_by=updated_at
+ # GET /projects/:id/merge_requests?sort=desc
+ # GET /projects/:id/merge_requests?sort=asc
#
get ":id/merge_requests" do
authorize! :read_merge_request, user_project
+ merge_requests = user_project.merge_requests
- mrs = case params["state"]
- when "opened" then user_project.merge_requests.opened
- when "closed" then user_project.merge_requests.closed
- when "merged" then user_project.merge_requests.merged
- else user_project.merge_requests
- end
+ merge_requests =
+ case params["state"]
+ when "opened" then merge_requests.opened
+ when "closed" then merge_requests.closed
+ when "merged" then merge_requests.merged
+ else merge_requests
+ end
- present paginate(mrs), with: Entities::MergeRequest
+ merge_requests.reorder(issuable_order_by => issuable_sort)
+ present paginate(merge_requests), with: Entities::MergeRequest
end
# Show MR
@@ -56,6 +70,22 @@ module API
present merge_request, with: Entities::MergeRequest
end
+ # Show MR changes
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_request/:merge_request_id/changes
+ #
+ get ':id/merge_request/:merge_request_id/changes' do
+ merge_request = user_project.merge_requests.
+ find(params[:merge_request_id])
+ authorize! :read_merge_request, merge_request
+ present merge_request, with: Entities::MergeRequestChanges
+ end
+
# Create MR
#
# Parameters:
@@ -148,13 +178,10 @@ module API
put ":id/merge_request/:merge_request_id/merge" do
merge_request = user_project.merge_requests.find(params[:merge_request_id])
- action = if user_project.protected_branch?(merge_request.target_branch)
- :push_code_to_protected_branches
- else
- :push_code
- end
+ allowed = ::Gitlab::GitAccess.new(current_user, user_project).
+ can_push_to_branch?(merge_request.target_branch)
- if can?(current_user, action, user_project)
+ if allowed
if merge_request.unchecked?
merge_request.check_if_can_be_merged
end
@@ -214,7 +241,7 @@ module API
if note.save
present note, with: Entities::MRNote
else
- not_found!
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
end
end
end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index a4fdb752d6..c5cd73943f 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -48,7 +48,7 @@ module API
if milestone.valid?
present milestone, with: Entities::Milestone
else
- not_found!
+ render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
end
end
@@ -72,9 +72,24 @@ module API
if milestone.valid?
present milestone, with: Entities::Milestone
else
- not_found!
+ render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
end
end
+
+ # Get all issues for a single project milestone
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # milestone_id (required) - The ID of a project milestone
+ # Example Request:
+ # GET /projects/:id/milestones/:milestone_id/issues
+ get ":id/milestones/:milestone_id/issues" do
+ authorize! :read_milestone, user_project
+
+ @milestone = user_project.milestones.find(params[:milestone_id])
+ present paginate(@milestone.issues), with: Entities::Issue
+ end
+
end
end
end
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index f9f2ed90cc..b90ed6af5f 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -1,10 +1,10 @@
module API
# namespaces API
class Namespaces < Grape::API
- before {
+ before do
authenticate!
authenticated_as_admin!
- }
+ end
resource :namespaces do
# Get a namespaces list
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 0ef9a3c4be..3726be7c53 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -61,9 +61,42 @@ module API
if @note.valid?
present @note, with: Entities::Note
else
- not_found!
+ not_found!("Note #{@note.errors.messages}")
end
end
+
+ # Modify existing +noteable+ note
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # noteable_id (required) - The ID of an issue or snippet
+ # node_id (required) - The ID of a note
+ # body (required) - New content of a note
+ # Example Request:
+ # PUT /projects/:id/issues/:noteable_id/notes/:note_id
+ # PUT /projects/:id/snippets/:noteable_id/notes/:node_id
+ put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
+ required_attributes! [:body]
+
+ authorize! :admin_note, user_project.notes.find(params[:note_id])
+
+ opts = {
+ note: params[:body],
+ note_id: params[:note_id],
+ noteable_type: noteables_str.classify,
+ noteable_id: params[noteable_id_str]
+ }
+
+ @note = ::Notes::UpdateService.new(user_project, current_user,
+ opts).execute
+
+ if @note.valid?
+ present @note, with: Entities::Note
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
+
end
end
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 79c3d122d3..be9850367b 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -38,7 +38,13 @@ module API
# POST /projects/:id/hooks
post ":id/hooks" do
required_attributes! [:url]
- attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events]
+ attrs = attributes_for_keys [
+ :url,
+ :push_events,
+ :issues_events,
+ :merge_requests_events,
+ :tag_push_events
+ ]
@hook = user_project.hooks.new(attrs)
if @hook.save
@@ -47,7 +53,7 @@ module API
if @hook.errors[:url].present?
error!("Invalid url given", 422)
end
- not_found!
+ not_found!("Project hook #{@hook.errors.messages}")
end
end
@@ -62,7 +68,13 @@ module API
put ":id/hooks/:hook_id" do
@hook = user_project.hooks.find(params[:hook_id])
required_attributes! [:url]
- attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events]
+ attrs = attributes_for_keys [
+ :url,
+ :push_events,
+ :issues_events,
+ :merge_requests_events,
+ :tag_push_events
+ ]
if @hook.update_attributes attrs
present @hook, with: Entities::ProjectHook
@@ -70,7 +82,7 @@ module API
if @hook.errors[:url].present?
error!("Invalid url given", 422)
end
- not_found!
+ not_found!("Project hook #{@hook.errors.messages}")
end
end
diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb
index 47c4ddce16..c756bb479f 100644
--- a/lib/api/project_members.rb
+++ b/lib/api/project_members.rb
@@ -4,14 +4,6 @@ module API
before { authenticate! }
resource :projects do
- helpers do
- def handle_project_member_errors(errors)
- if errors[:project_access].any?
- error!(errors[:project_access], 422)
- end
- not_found!
- end
- end
# Get a project team members
#
@@ -54,19 +46,19 @@ module API
required_attributes! [:user_id, :access_level]
# either the user is already a team member or a new one
- team_member = user_project.team_member_by_id(params[:user_id])
- if team_member.nil?
- team_member = user_project.users_projects.new(
+ project_member = user_project.project_member_by_id(params[:user_id])
+ if project_member.nil?
+ project_member = user_project.project_members.new(
user_id: params[:user_id],
- project_access: params[:access_level]
+ access_level: params[:access_level]
)
end
- if team_member.save
- @member = team_member.user
+ if project_member.save
+ @member = project_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- handle_project_member_errors team_member.errors
+ handle_member_errors project_member.errors
end
end
@@ -82,14 +74,14 @@ module API
authorize! :admin_project, user_project
required_attributes! [:access_level]
- team_member = user_project.users_projects.find_by(user_id: params[:user_id])
- not_found!("User can not be found") if team_member.nil?
+ project_member = user_project.project_members.find_by(user_id: params[:user_id])
+ not_found!("User can not be found") if project_member.nil?
- if team_member.update_attributes(project_access: params[:access_level])
- @member = team_member.user
+ if project_member.update_attributes(access_level: params[:access_level])
+ @member = project_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- handle_project_member_errors team_member.errors
+ handle_member_errors project_member.errors
end
end
@@ -102,11 +94,11 @@ module API
# DELETE /projects/:id/members/:user_id
delete ":id/members/:user_id" do
authorize! :admin_project, user_project
- team_member = user_project.users_projects.find_by(user_id: params[:user_id])
- unless team_member.nil?
- team_member.destroy
+ project_member = user_project.project_members.find_by(user_id: params[:user_id])
+ unless project_member.nil?
+ project_member.destroy
else
- {message: "Access revoked", id: params[:user_id].to_i}
+ { message: "Access revoked", id: params[:user_id].to_i }
end
end
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 8e09fff684..54f2555903 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -42,21 +42,22 @@ module API
# title (required) - The title of a snippet
# file_name (required) - The name of a snippet file
# code (required) - The content of a snippet
+ # visibility_level (required) - The snippet's visibility
# Example Request:
# POST /projects/:id/snippets
post ":id/snippets" do
authorize! :write_project_snippet, user_project
- required_attributes! [:title, :file_name, :code]
+ required_attributes! [:title, :file_name, :code, :visibility_level]
- attrs = attributes_for_keys [:title, :file_name]
+ attrs = attributes_for_keys [:title, :file_name, :visibility_level]
attrs[:content] = params[:code] if params[:code].present?
- @snippet = user_project.snippets.new attrs
- @snippet.author = current_user
+ @snippet = CreateSnippetService.new(user_project, current_user,
+ attrs).execute
- if @snippet.save
- present @snippet, with: Entities::ProjectSnippet
+ if @snippet.errors.any?
+ render_validation_error!(@snippet)
else
- not_found!
+ present @snippet, with: Entities::ProjectSnippet
end
end
@@ -68,19 +69,22 @@ module API
# title (optional) - The title of a snippet
# file_name (optional) - The name of a snippet file
# code (optional) - The content of a snippet
+ # visibility_level (optional) - The snippet's visibility
# Example Request:
# PUT /projects/:id/snippets/:snippet_id
put ":id/snippets/:snippet_id" do
@snippet = user_project.snippets.find(params[:snippet_id])
authorize! :modify_project_snippet, @snippet
- attrs = attributes_for_keys [:title, :file_name]
+ attrs = attributes_for_keys [:title, :file_name, :visibility_level]
attrs[:content] = params[:code] if params[:code].present?
- if @snippet.update_attributes attrs
- present @snippet, with: Entities::ProjectSnippet
+ UpdateSnippetService.new(user_project, current_user, @snippet,
+ attrs).execute
+ if @snippet.errors.any?
+ render_validation_error!(@snippet)
else
- not_found!
+ present @snippet, with: Entities::ProjectSnippet
end
end
@@ -97,6 +101,7 @@ module API
authorize! :modify_project_snippet, @snippet
@snippet.destroy
rescue
+ not_found!('Snippet')
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 55f7975bbf..e3fff79d68 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -11,23 +11,46 @@ module API
attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
attrs
end
+
+ def filter_projects(projects)
+ # If the archived parameter is passed, limit results accordingly
+ if params[:archived].present?
+ projects = projects.where(archived: parse_boolean(params[:archived]))
+ end
+
+ if params[:search].present?
+ projects = projects.search(params[:search])
+ end
+
+ projects.reorder(project_order_by => project_sort)
+ end
+
+ def project_order_by
+ order_fields = %w(id name path created_at updated_at last_activity_at)
+
+ if order_fields.include?(params['order_by'])
+ params['order_by']
+ else
+ 'created_at'
+ end
+ end
+
+ def project_sort
+ if params["sort"] == 'asc'
+ :asc
+ else
+ :desc
+ end
+ end
end
# Get a projects list for authenticated user
#
- # Parameters:
- # archived (optional) - if passed, limit by archived status
- #
# Example Request:
# GET /projects
get do
@projects = current_user.authorized_projects
-
- # If the archived parameter is passed, limit results accordingly
- if params[:archived].present?
- @projects = @projects.where(archived: parse_boolean(params[:archived]))
- end
-
+ @projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -37,7 +60,9 @@ module API
# Example Request:
# GET /projects/owned
get '/owned' do
- @projects = paginate current_user.owned_projects
+ @projects = current_user.owned_projects
+ @projects = filter_projects(@projects)
+ @projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -47,7 +72,9 @@ module API
# GET /projects/all
get '/all' do
authenticated_as_admin!
- @projects = paginate Project
+ @projects = Project.all
+ @projects = filter_projects(@projects)
+ @projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -61,17 +88,14 @@ module API
present user_project, with: Entities::ProjectWithAccess, user: current_user
end
- # Get a single project events
+ # Get events for a single project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
- # GET /projects/:id
+ # GET /projects/:id/events
get ":id/events" do
- limit = (params[:per_page] || 20).to_i
- offset = (params[:page] || 0).to_i * limit
- events = user_project.events.recent.limit(limit).offset(offset)
-
+ events = paginate user_project.events.recent
present events, with: Entities::Event
end
@@ -111,7 +135,7 @@ module API
if @project.errors[:limit_reached].present?
error!(@project.errors[:limit_reached], 403)
end
- not_found!
+ render_validation_error!(@project)
end
end
@@ -149,7 +173,67 @@ module API
if @project.saved?
present @project, with: Entities::Project
else
- not_found!
+ render_validation_error!(@project)
+ end
+ end
+
+ # Fork new project for the current user.
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request
+ # POST /projects/fork/:id
+ post 'fork/:id' do
+ @forked_project =
+ ::Projects::ForkService.new(user_project,
+ current_user).execute
+ if @forked_project.errors.any?
+ conflict!(@forked_project.errors.messages)
+ else
+ present @forked_project, with: Entities::Project
+ end
+ end
+
+ # Update an existing project
+ #
+ # Parameters:
+ # id (required) - the id of a project
+ # name (optional) - name of a project
+ # path (optional) - path of a project
+ # description (optional) - short project description
+ # issues_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # public (optional) - if true same as setting visibility_level = 20
+ # visibility_level (optional) - visibility level of a project
+ # Example Request
+ # PUT /projects/:id
+ put ':id' do
+ attrs = attributes_for_keys [:name,
+ :path,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :public,
+ :visibility_level]
+ attrs = map_public_to_visibility_level(attrs)
+ authorize_admin_project
+ authorize! :rename_project, user_project if attrs[:name].present?
+ if attrs[:visibility_level].present?
+ authorize! :change_visibility_level, user_project
+ end
+
+ ::Projects::UpdateService.new(user_project,
+ current_user, attrs).execute
+
+ if user_project.errors.any?
+ render_validation_error!(user_project)
+ else
+ present user_project, with: Entities::Project
end
end
@@ -161,7 +245,7 @@ module API
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
- user_project.destroy
+ ::Projects::DestroyService.new(user_project, current_user, {}).execute
end
# Mark this project as forked from another
@@ -181,7 +265,7 @@ module API
render_api_error!("Project already forked", 409)
end
else
- not_found!
+ not_found!("Source Project")
end
end
@@ -210,6 +294,16 @@ module API
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
+
present paginate(projects), with: Entities::Project
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 42068bb343..1fbf3dca3c 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -23,7 +23,8 @@ module API
# Example Request:
# GET /projects/:id/repository/tags
get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project
+ present user_project.repo.tags.sort_by(&:name).reverse,
+ with: Entities::RepoTag, project: user_project
end
# Create tag
@@ -32,14 +33,22 @@ module API
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# ref (required) - Create tag from commit sha or branch
+ # message (optional) - Specifying a message creates an annotated tag.
# Example Request:
# POST /projects/:id/repository/tags
post ':id/repository/tags' do
authorize_push_project
- @tag = CreateTagService.new.execute(user_project, params[:tag_name],
- params[:ref], current_user)
+ message = params[:message] || nil
+ result = CreateTagService.new(user_project, current_user).
+ execute(params[:tag_name], params[:ref], message)
- present @tag, with: Entities::RepoObject, project: user_project
+ if result[:status] == :success
+ present result[:tag],
+ with: Entities::RepoTag,
+ project: user_project
+ else
+ render_api_error!(result[:message], 400)
+ end
end
# Get a project repository tree
@@ -49,11 +58,13 @@ module API
# ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
# Example Request:
# GET /projects/:id/repository/tree
- get ":id/repository/tree" do
+ get ':id/repository/tree' do
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
path = params[:path] || nil
commit = user_project.repository.commit(ref)
+ not_found!('Tree') unless commit
+
tree = user_project.repository.tree(commit.id, path)
present tree.sorted_entries, with: Entities::RepoTreeObject
@@ -91,14 +102,18 @@ module API
# sha (required) - The blob's sha
# Example Request:
# GET /projects/:id/repository/raw_blobs/:sha
- get ":id/repository/raw_blobs/:sha" do
+ get ':id/repository/raw_blobs/:sha' do
ref = params[:sha]
repo = user_project.repository
- blob = Gitlab::Git::Blob.raw(repo, ref)
+ begin
+ blob = Gitlab::Git::Blob.raw(repo, ref)
+ rescue
+ not_found! 'Blob'
+ end
- not_found! "Blob" unless blob
+ not_found! 'Blob' unless blob
env['api.format'] = :txt
@@ -113,18 +128,29 @@ module API
# sha (optional) - the commit sha to download defaults to the tip of the default branch
# Example Request:
# GET /projects/:id/repository/archive
- get ":id/repository/archive", requirements: { format: Gitlab::Regex.archive_formats_regex } do
+ get ':id/repository/archive',
+ requirements: { format: Gitlab::Regex.archive_formats_regex } do
authorize! :download_code, user_project
- file_path = ArchiveRepositoryService.new.execute(user_project, params[:sha], params[:format])
+
+ begin
+ file_path = ArchiveRepositoryService.new(
+ user_project,
+ params[:sha],
+ params[:format]
+ ).execute
+ rescue
+ not_found!('File')
+ end
if file_path && File.exists?(file_path)
data = File.open(file_path, 'rb').read
- header["Content-Disposition"] = "attachment; filename=\"#{File.basename(file_path)}\""
+ basename = File.basename(file_path)
+ header['Content-Disposition'] = "attachment; filename=\"#{basename}\""
content_type MIME::Types.type_for(file_path).first.content_type
env['api.format'] = :binary
present data
else
- not_found!
+ redirect request.fullpath
end
end
@@ -152,7 +178,12 @@ module API
get ':id/repository/contributors' do
authorize! :download_code, user_project
- present user_project.repository.contributors, with: Entities::Contributor
+ begin
+ present user_project.repository.contributors,
+ with: Entities::Contributor
+ rescue
+ not_found!
+ end
end
end
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index bde502e32e..3ad59cf3ad 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -28,7 +28,7 @@ module API
# Delete GitLab CI service settings
#
# Example Request:
- # DELETE /projects/:id/keys/:id
+ # DELETE /projects/:id/services/gitlab-ci
delete ":id/services/gitlab-ci" do
if user_project.gitlab_ci_service
user_project.gitlab_ci_service.update_attributes(
@@ -38,7 +38,41 @@ module API
)
end
end
+
+ # Set Hipchat service for project
+ #
+ # Parameters:
+ # token (required) - Hipchat token
+ # room (required) - Hipchat room name
+ #
+ # Example Request:
+ # PUT /projects/:id/services/hipchat
+ put ':id/services/hipchat' do
+ required_attributes! [:token, :room]
+ attrs = attributes_for_keys [:token, :room]
+ user_project.build_missing_services
+
+ if user_project.hipchat_service.update_attributes(
+ attrs.merge(active: true))
+ true
+ else
+ not_found!
+ end
+ end
+
+ # Delete Hipchat service settings
+ #
+ # Example Request:
+ # DELETE /projects/:id/services/hipchat
+ delete ':id/services/hipchat' do
+ if user_project.hipchat_service
+ user_project.hipchat_service.update_attributes(
+ active: false,
+ token: nil,
+ room: nil
+ )
+ end
+ end
end
end
end
-
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 3e239c5afe..518964db50 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -1,10 +1,10 @@
module API
# Hooks API
class SystemHooks < Grape::API
- before {
+ before do
authenticate!
authenticated_as_admin!
- }
+ end
resource :hooks do
# Get the list of system hooks
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 69553f1639..032a5d76e4 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -42,7 +42,8 @@ module API
# Parameters:
# email (required) - Email
# password (required) - Password
- # name - Name
+ # name (required) - Name
+ # username (required) - Name
# skype - Skype ID
# linkedin - Linkedin
# twitter - Twitter account
@@ -53,19 +54,36 @@ module API
# bio - Bio
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
+ # confirm - Require user confirmation - true (default) or false
# Example Request:
# POST /users
post do
authenticated_as_admin!
required_attributes! [:email, :password, :name, :username]
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
- user = User.build_user(attrs)
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin, :confirm]
admin = attrs.delete(:admin)
+ confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i))
+ user = User.build_user(attrs)
user.admin = admin unless admin.nil?
+ user.skip_confirmation! unless confirm
+
+ identity_attrs = attributes_for_keys [:provider, :extern_uid]
+ if identity_attrs.any?
+ user.identities.build(identity_attrs)
+ end
+
if user.save
present user, with: Entities::UserFull
else
- not_found!
+ conflict!('Email has already been taken') if User.
+ where(email: user.email).
+ count > 0
+
+ conflict!('Username has already been taken') if User.
+ where(username: user.username).
+ count > 0
+
+ render_validation_error!(user)
end
end
@@ -80,8 +98,6 @@ module API
# twitter - Twitter account
# website_url - Website url
# projects_limit - Limit projects each user can create
- # extern_uid - External authentication provider UID
- # provider - External provider
# bio - Bio
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
@@ -90,16 +106,25 @@ module API
put ":id" do
authenticated_as_admin!
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.find(params[:id])
- not_found!("User not found") unless user
+ not_found!('User') unless user
admin = attrs.delete(:admin)
user.admin = admin unless admin.nil?
+
+ conflict!('Email has already been taken') if attrs[:email] &&
+ User.where(email: attrs[:email]).
+ where.not(id: user.id).count > 0
+
+ conflict!('Username has already been taken') if attrs[:username] &&
+ User.where(username: attrs[:username]).
+ where.not(id: user.id).count > 0
+
if user.update_attributes(attrs)
present user, with: Entities::UserFull
else
- not_found!
+ render_validation_error!(user)
end
end
@@ -113,13 +138,15 @@ module API
# POST /users/:id/keys
post ":id/keys" do
authenticated_as_admin!
+ required_attributes! [:title, :key]
+
user = User.find(params[:id])
attrs = attributes_for_keys [:title, :key]
key = user.keys.new attrs
if key.save
present key, with: Entities::SSHKey
else
- not_found!
+ render_validation_error!(key)
end
end
@@ -132,11 +159,9 @@ module API
get ':uid/keys' do
authenticated_as_admin!
user = User.find_by(id: params[:uid])
- if user
- present user.keys, with: Entities::SSHKey
- else
- not_found!
- end
+ not_found!('User') unless user
+
+ present user.keys, with: Entities::SSHKey
end
# Delete existing ssh key of a specified user. Only available to admin
@@ -150,15 +175,13 @@ module API
delete ':uid/keys/:id' do
authenticated_as_admin!
user = User.find_by(id: params[:uid])
- if user
- begin
- key = user.keys.find params[:id]
- key.destroy
- rescue ActiveRecord::RecordNotFound
- not_found!
- end
- else
- not_found!
+ not_found!('User') unless user
+
+ begin
+ key = user.keys.find params[:id]
+ key.destroy
+ rescue ActiveRecord::RecordNotFound
+ not_found!('Key')
end
end
@@ -173,7 +196,7 @@ module API
if user
user.destroy
else
- not_found!
+ not_found!('User')
end
end
end
@@ -219,7 +242,7 @@ module API
if key.save
present key, with: Entities::SSHKey
else
- not_found!
+ render_validation_error!(key)
end
end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 7b6908ccad..9ab6aca276 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -13,30 +13,33 @@ module Backup
def dump
success = case config["adapter"]
when /^mysql/ then
- print "Dumping MySQL database #{config['database']} ... "
+ $progress.print "Dumping MySQL database #{config['database']} ... "
system('mysqldump', *mysql_args, config['database'], out: db_file_name)
when "postgresql" then
- print "Dumping PostgreSQL database #{config['database']} ... "
+ $progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
system('pg_dump', config['database'], out: db_file_name)
end
report_success(success)
+ abort 'Backup failed' unless success
end
def restore
success = case config["adapter"]
when /^mysql/ then
- print "Restoring MySQL database #{config['database']} ... "
+ $progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
- print "Restoring PostgreSQL database #{config['database']} ... "
+ $progress.print "Restoring PostgreSQL database #{config['database']} ... "
# Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
# statements like MySQL.
Rake::Task["gitlab:db:drop_all_tables"].invoke
+ Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke
pg_env
system('psql', config['database'], '-f', db_file_name)
end
report_success(success)
+ abort 'Restore failed' unless success
end
protected
@@ -66,9 +69,9 @@ module Backup
def report_success(success)
if success
- puts '[DONE]'.green
+ $progress.puts '[DONE]'.green
else
- puts '[FAILED]'.red
+ $progress.puts '[FAILED]'.red
end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 28e323fe30..b69aebf9fe 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -1,7 +1,5 @@
module Backup
class Manager
- BACKUP_CONTENTS = %w{repositories/ db/ uploads/ backup_information.yml}
-
def pack
# saving additional informations
s = {}
@@ -9,51 +7,91 @@ module Backup
s[:backup_created_at] = Time.now
s[:gitlab_version] = Gitlab::VERSION
s[:tar_version] = tar_version
+ s[:skipped] = ENV["SKIP"]
+ tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar"
- Dir.chdir(Gitlab.config.backup.path)
+ Dir.chdir(Gitlab.config.backup.path) do
+ File.open("#{Gitlab.config.backup.path}/backup_information.yml",
+ "w+") do |file|
+ file << s.to_yaml.gsub(/^---\n/,'')
+ end
- File.open("#{Gitlab.config.backup.path}/backup_information.yml", "w+") do |file|
- file << s.to_yaml.gsub(/^---\n/,'')
+ FileUtils.chmod(0700, folders_to_backup)
+
+ # create archive
+ $progress.print "Creating backup archive: #{tar_file} ... "
+ orig_umask = File.umask(0077)
+ if Kernel.system('tar', '-cf', tar_file, *backup_contents)
+ $progress.puts "done".green
+ else
+ puts "creating archive #{tar_file} failed".red
+ abort 'Backup failed'
+ end
+ File.umask(orig_umask)
+
+ upload(tar_file)
+ end
+ end
+
+ def upload(tar_file)
+ remote_directory = Gitlab.config.backup.upload.remote_directory
+ $progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
+
+ connection_settings = Gitlab.config.backup.upload.connection
+ if connection_settings.blank?
+ $progress.puts "skipped".yellow
+ return
end
- # create archive
- print "Creating backup archive: #{s[:backup_created_at].to_i}_gitlab_backup.tar ... "
- if Kernel.system('tar', '-cf', "#{s[:backup_created_at].to_i}_gitlab_backup.tar", *BACKUP_CONTENTS)
- puts "done".green
+ connection = ::Fog::Storage.new(connection_settings)
+ directory = connection.directories.get(remote_directory)
+
+ if directory.files.create(key: tar_file, body: File.open(tar_file), public: false)
+ $progress.puts "done".green
else
- puts "failed".red
+ puts "uploading backup to #{remote_directory} failed".red
+ abort 'Backup failed'
end
end
def cleanup
- print "Deleting tmp directories ... "
- if Kernel.system('rm', '-rf', *BACKUP_CONTENTS)
- puts "done".green
- else
- puts "failed".red
+ $progress.print "Deleting tmp directories ... "
+
+ backup_contents.each do |dir|
+ next unless File.exist?(File.join(Gitlab.config.backup.path, dir))
+
+ if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir))
+ $progress.puts "done".green
+ else
+ puts "deleting tmp directory '#{dir}' failed".red
+ abort 'Backup failed'
+ end
end
end
def remove_old
# delete backups
- print "Deleting old backups ... "
+ $progress.print "Deleting old backups ... "
keep_time = Gitlab.config.backup.keep_time.to_i
- path = Gitlab.config.backup.path
if keep_time > 0
removed = 0
- file_list = Dir.glob(Rails.root.join(path, "*_gitlab_backup.tar"))
- file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
- file_list.sort.each do |timestamp|
- if Time.at(timestamp) < (Time.now - keep_time)
- if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar))
- removed += 1
+
+ Dir.chdir(Gitlab.config.backup.path) do
+ file_list = Dir.glob('*_gitlab_backup.tar')
+ file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
+ file_list.sort.each do |timestamp|
+ if Time.at(timestamp) < (Time.now - keep_time)
+ if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar))
+ removed += 1
+ end
end
end
end
- puts "done. (#{removed} removed)".green
+
+ $progress.puts "done. (#{removed} removed)".green
else
- puts "skipping".yellow
+ $progress.puts "skipping".yellow
end
end
@@ -63,6 +101,7 @@ module Backup
# check for existing backups in the backup dir
file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
puts "no backups found" if file_list.count == 0
+
if file_list.count > 1 && ENV["BACKUP"].nil?
puts "Found more than one backup, please specify which one you want to restore:"
puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
@@ -76,15 +115,15 @@ module Backup
exit 1
end
- print "Unpacking backup ... "
+ $progress.print "Unpacking backup ... "
+
unless Kernel.system(*%W(tar -xf #{tar_file}))
- puts "failed".red
+ puts "unpacking backup failed".red
exit 1
else
- puts "done".green
+ $progress.puts "done".green
end
- settings = YAML.load_file("backup_information.yml")
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
# restoring mismatching backups can lead to unexpected problems
@@ -103,5 +142,29 @@ module Backup
tar_version, _ = Gitlab::Popen.popen(%W(tar --version))
tar_version.force_encoding('locale').split("\n").first
end
+
+ def skipped?(item)
+ settings[:skipped] && settings[:skipped].include?(item)
+ end
+
+ private
+
+ def backup_contents
+ folders_to_backup + ["backup_information.yml"]
+ end
+
+ def folders_to_backup
+ folders = %w{repositories db uploads}
+
+ if ENV["SKIP"]
+ return folders.reject{ |folder| ENV["SKIP"].include?(folder) }
+ end
+
+ folders
+ end
+
+ def settings
+ @settings ||= YAML.load_file("backup_information.yml")
+ end
end
end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 6f7c4f7c90..dfb2da9f84 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -8,29 +8,42 @@ module Backup
prepare
Project.find_each(batch_size: 1000) do |project|
- print " * #{project.path_with_namespace} ... "
+ $progress.print " * #{project.path_with_namespace} ... "
# Create namespace dir if missing
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
if project.empty_repo?
- puts "[SKIPPED]".cyan
- elsif system(*%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all), silent)
- puts "[DONE]".green
+ $progress.puts "[SKIPPED]".cyan
else
- puts "[FAILED]".red
+ cmd = %W(tar -cf #{path_to_bundle(project)} -C #{path_to_repo(project)} .)
+ output, status = Gitlab::Popen.popen(cmd)
+ if status.zero?
+ $progress.puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
+ puts output
+ abort 'Backup failed'
+ end
end
wiki = ProjectWiki.new(project)
if File.exists?(path_to_repo(wiki))
- print " * #{wiki.path_with_namespace} ... "
- if wiki.empty?
- puts " [SKIPPED]".cyan
- elsif system(*%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all), silent)
- puts " [DONE]".green
+ $progress.print " * #{wiki.path_with_namespace} ... "
+ if wiki.repository.empty?
+ $progress.puts " [SKIPPED]".cyan
else
- puts " [FAILED]".red
+ cmd = %W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)
+ output, status = Gitlab::Popen.popen(cmd)
+ if status.zero?
+ $progress.puts " [DONE]".green
+ else
+ puts " [FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
+ abort 'Backup failed'
+ end
end
end
end
@@ -46,33 +59,53 @@ module Backup
FileUtils.mkdir_p(repos_path)
Project.find_each(batch_size: 1000) do |project|
- print "#{project.path_with_namespace} ... "
+ $progress.print " * #{project.path_with_namespace} ... "
project.namespace.ensure_dir_exist if project.namespace
- if system(*%W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)}), silent)
- puts "[DONE]".green
+ if File.exists?(path_to_bundle(project))
+ FileUtils.mkdir_p(path_to_repo(project))
+ cmd = %W(tar -xf #{path_to_bundle(project)} -C #{path_to_repo(project)})
+ else
+ cmd = %W(git init --bare #{path_to_repo(project)})
+ end
+
+ if system(*cmd, silent)
+ $progress.puts "[DONE]".green
else
puts "[FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
+ abort 'Restore failed'
end
wiki = ProjectWiki.new(project)
if File.exists?(path_to_bundle(wiki))
- print " * #{wiki.path_with_namespace} ... "
- if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent)
- puts " [DONE]".green
+ $progress.print " * #{wiki.path_with_namespace} ... "
+
+ # If a wiki bundle exists, first remove the empty repo
+ # that was initialized with ProjectWiki.new() and then
+ # try to restore with 'git clone --bare'.
+ FileUtils.rm_rf(path_to_repo(wiki))
+ cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)})
+
+ if system(*cmd, silent)
+ $progress.puts " [DONE]".green
else
puts " [FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
+ abort 'Restore failed'
end
end
end
- print 'Put GitLab hooks in repositories dirs'.yellow
- if system("#{Gitlab.config.gitlab_shell.path}/support/rewrite-hooks.sh", Gitlab.config.gitlab_shell.repos_path)
- puts " [DONE]".green
+ $progress.print 'Put GitLab hooks in repositories dirs'.yellow
+ cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks"
+ if system(cmd)
+ $progress.puts " [DONE]".green
else
puts " [FAILED]".red
+ puts "failed: #{cmd}"
end
end
@@ -80,7 +113,7 @@ module Backup
protected
def path_to_repo(project)
- File.join(repos_path, project.path_with_namespace + '.git')
+ project.repository.path_to_repo
end
def path_to_bundle(project)
diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb
new file mode 100644
index 0000000000..1b80be112a
--- /dev/null
+++ b/lib/disable_email_interceptor.rb
@@ -0,0 +1,8 @@
+# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
+class DisableEmailInterceptor
+
+ def self.delivering_email(message)
+ message.perform_deliveries = false
+ Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
+ end
+end
diff --git a/lib/email_validator.rb b/lib/email_validator.rb
index 0a67ebcd79..f509f0a584 100644
--- a/lib/email_validator.rb
+++ b/lib/email_validator.rb
@@ -1,5 +1,5 @@
# Based on https://github.com/balexand/email_validator
-#
+#
# Extended to use only strict mode with following allowed characters:
# ' - apostrophe
#
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
index 9b4b8c3801..163937c02c 100644
--- a/lib/event_filter.rb
+++ b/lib/event_filter.rb
@@ -23,7 +23,7 @@ class EventFilter
end
end
- def initialize params
+ def initialize(params)
@params = if params
params.dup
else
@@ -31,7 +31,7 @@ class EventFilter
end
end
- def apply_filter events
+ def apply_filter(events)
return events unless params.present?
filter = params.dup
@@ -50,7 +50,7 @@ class EventFilter
events = events.where(action: actions)
end
- def options key
+ def options(key)
filter = params.dup
if filter.include? key
@@ -62,7 +62,7 @@ class EventFilter
filter
end
- def active? key
+ def active?(key)
params.include? key
end
end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index e51cb30bdd..6e4ed01e07 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -1,17 +1,9 @@
# Module providing methods for dealing with separating a tree-ish string and a
# file path string when combined in a request parameter
module ExtractsPath
- extend ActiveSupport::Concern
-
# Raised when given an invalid file path
class InvalidPathError < StandardError; end
- included do
- if respond_to?(:before_filter)
- before_filter :assign_ref_vars
- end
- end
-
# Given a string containing both a Git tree-ish, such as a branch or tag, and
# a filesystem path joined by forward slashes, attempts to separate the two.
#
@@ -110,7 +102,8 @@ module ExtractsPath
raise InvalidPathError unless @commit
@hex_path = Digest::SHA1.hexdigest(@path)
- @logs_path = logs_file_project_ref_path(@project, @ref, @path)
+ @logs_path = logs_file_namespace_project_ref_path(@project.namespace,
+ @project, @ref, @path)
rescue RuntimeError, NoMethodError, InvalidPathError
not_found!
diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb
index 42970c1be5..2eae55e534 100644
--- a/lib/file_size_validator.rb
+++ b/lib/file_size_validator.rb
@@ -25,8 +25,8 @@ class FileSizeValidator < ActiveModel::EachValidator
keys.each do |key|
value = options[key]
- unless value.is_a?(Integer) && value >= 0
- raise ArgumentError, ":#{key} must be a nonnegative Integer"
+ unless (value.is_a?(Integer) && value >= 0) || value.is_a?(Symbol)
+ raise ArgumentError, ":#{key} must be a nonnegative Integer or symbol"
end
end
end
@@ -39,6 +39,14 @@ class FileSizeValidator < ActiveModel::EachValidator
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
+ check_value =
+ case check_value
+ when Integer
+ check_value
+ when Symbol
+ record.send(check_value)
+ end
+
value ||= [] if key == :maximum
value_size = value.size
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
new file mode 100644
index 0000000000..5fc1862c3e
--- /dev/null
+++ b/lib/gitlab.rb
@@ -0,0 +1,5 @@
+require 'gitlab/git'
+
+module Gitlab
+ autoload :Satellite, 'gitlab/satellite/satellite'
+end
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 87f9cfab60..424541b4a0 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -11,11 +11,20 @@ module Gitlab
MASTER = 40
OWNER = 50
+ # Branch protection settings
+ PROTECTION_NONE = 0
+ PROTECTION_DEV_CAN_PUSH = 1
+ PROTECTION_FULL = 2
+
class << self
def values
options.values
end
+ def all_values
+ options_with_owner.values
+ end
+
def options
{
"Guest" => GUEST,
@@ -39,6 +48,18 @@ module Gitlab
master: MASTER,
}
end
+
+ def protection_options
+ {
+ "Not protected, developers and masters can (force) push and delete the branch" => PROTECTION_NONE,
+ "Partially protected, developers can also push but prevent all force pushes and deletion" => PROTECTION_DEV_CAN_PUSH,
+ "Fully protected, only masters can push and prevent all force pushes and deletion" => PROTECTION_FULL,
+ }
+ end
+
+ def protection_values
+ protection_options.values
+ end
end
def human_access
diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb
index 8e4717b46e..dddcb2538f 100644
--- a/lib/gitlab/app_logger.rb
+++ b/lib/gitlab/app_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
class AppLogger < Gitlab::Logger
- def self.file_name
- 'application.log'
+ def self.file_name_noext
+ 'application'
end
def format_message(severity, timestamp, progname, msg)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 955abc1bed..30509528b8 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,24 +1,18 @@
module Gitlab
class Auth
def find(login, password)
- user = User.find_by(email: login) || User.find_by(username: login)
+ user = User.by_login(login)
+ # If no user is found, or it's an LDAP server, try LDAP.
+ # LDAP users are only authenticated via LDAP
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
- return nil unless ldap_conf.enabled
+ return nil unless Gitlab::LDAP::Config.enabled?
- Gitlab::LDAP::User.authenticate(login, password)
+ Gitlab::LDAP::Authentication.login(login, password)
else
user if user.valid_password?(password)
end
end
-
- def log
- Gitlab::AppLogger
- end
-
- def ldap_conf
- @ldap_conf ||= Gitlab.config.ldap
- end
end
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index c2f3b851c0..050b5ba29d 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -1,3 +1,4 @@
+require_relative 'rack_attack_helpers'
require_relative 'shell_env'
module Grack
@@ -10,8 +11,9 @@ module Grack
@request = Rack::Request.new(env)
@auth = Request.new(env)
- # Need this patch due to the rails mount
+ @gitlab_ci = false
+ # Need this patch due to the rails mount
# Need this if under RELATIVE_URL_ROOT
unless Gitlab.config.gitlab.relative_url_root.empty?
# If website is mounted using relative_url_root need to remove it first
@@ -22,8 +24,12 @@ module Grack
@env['SCRIPT_NAME'] = ""
- if project
- auth!
+ auth!
+
+ if project && authorized_request?
+ @app.call(env)
+ elsif @user.nil? && !@gitlab_ci
+ unauthorized
else
render_not_found
end
@@ -32,35 +38,30 @@ module Grack
private
def auth!
- if @auth.provided?
- return bad_request unless @auth.basic?
+ return unless @auth.provided?
- # Authentication with username and password
- login, password = @auth.credentials
+ return bad_request unless @auth.basic?
- # Allow authentication for GitLab CI service
- # if valid token passed
- if gitlab_ci_request?(login, password)
- return @app.call(env)
- end
+ # Authentication with username and password
+ login, password = @auth.credentials
- @user = authenticate_user(login, password)
-
- if @user
- Gitlab::ShellEnv.set_env(@user)
- @env['REMOTE_USER'] = @auth.username
- end
+ # Allow authentication for GitLab CI service
+ # if valid token passed
+ if gitlab_ci_request?(login, password)
+ @gitlab_ci = true
+ return
end
- if authorized_request?
- @app.call(env)
- else
- unauthorized
+ @user = authenticate_user(login, password)
+
+ if @user
+ Gitlab::ShellEnv.set_env(@user)
+ @env['REMOTE_USER'] = @auth.username
end
end
def gitlab_ci_request?(login, password)
- if login == "gitlab-ci-token" && project.gitlab_ci?
+ if login == "gitlab-ci-token" && project && project.gitlab_ci?
token = project.gitlab_ci_service.token
if token.present? && token == password && git_cmd == 'git-upload-pack'
@@ -71,16 +72,64 @@ module Grack
false
end
+ def oauth_access_token_check(login, password)
+ if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present?
+ token = Doorkeeper::AccessToken.by_token(password)
+ token && token.accessible? && User.find_by(id: token.resource_owner_id)
+ end
+ end
+
def authenticate_user(login, password)
- auth = Gitlab::Auth.new
- auth.find(login, password)
+ user = Gitlab::Auth.new.find(login, password)
+
+ unless user
+ user = oauth_access_token_check(login, password)
+ end
+
+ # If the user authenticated successfully, we reset the auth failure count
+ # from Rack::Attack for that IP. A client may attempt to authenticate
+ # with a username and blank password first, and only after it receives
+ # a 401 error does it present a password. Resetting the count prevents
+ # false positives from occurring.
+ #
+ # Otherwise, we let Rack::Attack know there was a failed authentication
+ # attempt from this IP. This information is stored in the Rails cache
+ # (Redis) and will be used by the Rack::Attack middleware to decide
+ # whether to block requests from this IP.
+ config = Gitlab.config.rack_attack.git_basic_auth
+
+ if config.enabled
+ if user
+ # A successful login will reset the auth failure count from this IP
+ Rack::Attack::Allow2Ban.reset(@request.ip, config)
+ else
+ banned = Rack::Attack::Allow2Ban.filter(@request.ip, config) do
+ # Unless the IP is whitelisted, return true so that Allow2Ban
+ # increments the counter (stored in Rails.cache) for the IP
+ if config.ip_whitelist.include?(@request.ip)
+ false
+ else
+ true
+ end
+ end
+
+ if banned
+ Rails.logger.info "IP #{@request.ip} failed to login " \
+ "as #{login} but has been temporarily banned from Git auth"
+ end
+ end
+ end
+
+ user
end
def authorized_request?
+ return true if @gitlab_ci
+
case git_cmd
when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
if user
- Gitlab::GitAccess.new.download_allowed?(user, project)
+ Gitlab::GitAccess.new(user, project).download_access_check.allowed?
elsif project.public?
# Allow clone/fetch for public projects
true
@@ -90,7 +139,7 @@ module Grack
when *Gitlab::GitAccess::PUSH_COMMANDS
if user
# Skip user authorization on upload request.
- # It will be serverd by update hook in repository
+ # It will be done by the pre-receive hook in the repository.
true
else
false
@@ -111,7 +160,9 @@ module Grack
end
def project
- @project ||= project_by_path(@request.path_info)
+ return @project if defined?(@project)
+
+ @project = project_by_path(@request.path_info)
end
def project_by_path(path)
@@ -119,12 +170,13 @@ module Grack
path_with_namespace = m.last
path_with_namespace.gsub!(/\.wiki$/, '')
+ path_with_namespace[0] = '' if path_with_namespace.start_with?('/')
Project.find_with_namespace(path_with_namespace)
end
end
def render_not_found
- [404, {"Content-Type" => "text/plain"}, ["Not Found"]]
+ [404, { "Content-Type" => "text/plain" }, ["Not Found"]]
end
end
end
diff --git a/lib/gitlab/backend/rack_attack_helpers.rb b/lib/gitlab/backend/rack_attack_helpers.rb
new file mode 100644
index 0000000000..8538f3f6ec
--- /dev/null
+++ b/lib/gitlab/backend/rack_attack_helpers.rb
@@ -0,0 +1,31 @@
+# rack-attack v4.2.0 doesn't yet support clearing of keys.
+# Taken from https://github.com/kickstarter/rack-attack/issues/113
+class Rack::Attack::Allow2Ban
+ def self.reset(discriminator, options)
+ findtime = options[:findtime] or raise ArgumentError, "Must pass findtime option"
+
+ cache.reset_count("#{key_prefix}:count:#{discriminator}", findtime)
+ cache.delete("#{key_prefix}:ban:#{discriminator}")
+ end
+end
+
+class Rack::Attack::Cache
+ def reset_count(unprefixed_key, period)
+ epoch_time = Time.now.to_i
+ # Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA
+ expires_in = period - (epoch_time % period) + 1
+ key = "#{(epoch_time / period).to_i}:#{unprefixed_key}"
+ delete(key)
+ end
+
+ def delete(unprefixed_key)
+ store.delete("#{prefix}:#{unprefixed_key}")
+ end
+end
+
+class Rack::Attack::StoreProxy::RedisStoreProxy
+ def delete(key, options={})
+ self.del(key)
+ rescue Redis::BaseError
+ end
+end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 53bff3037e..530f9d93de 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -8,6 +8,13 @@ module Gitlab
end
end
+ class << self
+ def version_required
+ @version_required ||= File.read(Rails.root.
+ join('GITLAB_SHELL_VERSION')).strip
+ end
+ end
+
# Init new repository
#
# name - project path with namespace
@@ -16,7 +23,8 @@ module Gitlab
# add_repository("gitlab/gitlab-ci")
#
def add_repository(name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path,
+ 'add-project', "#{name}.git"])
end
# Import repository
@@ -27,7 +35,8 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240'
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project',
+ "#{name}.git", url, '240'])
end
# Move repository
@@ -39,7 +48,8 @@ module Gitlab
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
#
def mv_repository(path, new_path)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
+ "#{path}.git", "#{new_path}.git"])
end
# Update HEAD for repository
@@ -51,7 +61,8 @@ module Gitlab
# update_repository_head("gitlab/gitlab-ci", "3-1-stable")
#
def update_repository_head(path, branch)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head',
+ "#{path}.git", branch])
end
# Fork repository to new namespace
@@ -63,7 +74,8 @@ module Gitlab
# fork_repository("gitlab/gitlab-ci", "randx")
#
def fork_repository(path, fork_namespace)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
+ "#{path}.git", fork_namespace])
end
# Remove repository from file system
@@ -74,7 +86,8 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci")
#
def remove_repository(name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path,
+ 'rm-project', "#{name}.git"])
end
# Add repository branch from passed ref
@@ -87,7 +100,8 @@ module Gitlab
# add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
#
def add_branch(path, branch_name, ref)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch',
+ "#{path}.git", branch_name, ref])
end
# Remove repository branch
@@ -99,7 +113,8 @@ module Gitlab
# rm_branch("gitlab/gitlab-ci", "4-0-stable")
#
def rm_branch(path, branch_name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch',
+ "#{path}.git", branch_name])
end
# Add repository tag from passed ref
@@ -107,12 +122,17 @@ module Gitlab
# path - project path with namespace
# tag_name - new tag name
# ref - HEAD for new tag
+ # message - optional message for tag (annotated tag)
#
# Ex.
# add_tag("gitlab/gitlab-ci", "v4.0", "master")
+ # add_tag("gitlab/gitlab-ci", "v4.0", "master", "message")
#
- def add_tag(path, tag_name, ref)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref
+ def add_tag(path, tag_name, ref, message = nil)
+ cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
+ #{tag_name} #{ref})
+ cmd << message unless message.nil? || message.empty?
+ Gitlab::Utils.system_silent(cmd)
end
# Remove repository tag
@@ -124,7 +144,8 @@ module Gitlab
# rm_tag("gitlab/gitlab-ci", "v4.0")
#
def rm_tag(path, tag_name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag',
+ "#{path}.git", tag_name])
end
# Add new key to gitlab-shell
@@ -133,7 +154,8 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
- system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path,
+ 'add-key', key_id, key_content])
end
# Batch-add keys to authorized_keys
@@ -152,7 +174,8 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...")
#
def remove_key(key_id, key_content)
- system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path,
+ 'rm-key', key_id, key_content])
end
# Remove all ssh keys from gitlab shell
@@ -161,7 +184,7 @@ module Gitlab
# remove_all_keys
#
def remove_all_keys
- system "#{gitlab_shell_path}/bin/gitlab-keys", "clear"
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear'])
end
# Add empty directory for storing repositories
@@ -208,7 +231,7 @@ module Gitlab
FileUtils.rm_r(satellites_path, force: true)
end
- def url_to_repo path
+ def url_to_repo(path)
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
end
@@ -217,7 +240,7 @@ module Gitlab
gitlab_shell_version_file = "#{gitlab_shell_path}/VERSION"
if File.readable?(gitlab_shell_version_file)
- File.read(gitlab_shell_version_file)
+ File.read(gitlab_shell_version_file).chomp
end
end
@@ -244,5 +267,13 @@ module Gitlab
def exists?(dir_name)
File.exists?(full_path(dir_name))
end
+
+ def gitlab_shell_projects_path
+ File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
+ end
+
+ def gitlab_shell_keys_path
+ File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
+ end
end
end
diff --git a/lib/gitlab/backend/shell_adapter.rb b/lib/gitlab/backend/shell_adapter.rb
index f247f4593d..fbe2a7a0d7 100644
--- a/lib/gitlab/backend/shell_adapter.rb
+++ b/lib/gitlab/backend/shell_adapter.rb
@@ -9,4 +9,3 @@ module Gitlab
end
end
end
-
diff --git a/lib/gitlab/bitbucket_import.rb b/lib/gitlab/bitbucket_import.rb
new file mode 100644
index 0000000000..7298152e7e
--- /dev/null
+++ b/lib/gitlab/bitbucket_import.rb
@@ -0,0 +1,6 @@
+module Gitlab
+ module BitbucketImport
+ mattr_accessor :public_key
+ @public_key = nil
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb
new file mode 100644
index 0000000000..5b1952b967
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/client.rb
@@ -0,0 +1,99 @@
+module Gitlab
+ module BitbucketImport
+ class Client
+ attr_reader :consumer, :api
+
+ def initialize(access_token = nil, access_token_secret = nil)
+ @consumer = ::OAuth::Consumer.new(
+ config.app_id,
+ config.app_secret,
+ bitbucket_options
+ )
+
+ if access_token && access_token_secret
+ @api = ::OAuth::AccessToken.new(@consumer, access_token, access_token_secret)
+ end
+ end
+
+ def request_token(redirect_uri)
+ request_token = consumer.get_request_token(oauth_callback: redirect_uri)
+
+ {
+ oauth_token: request_token.token,
+ oauth_token_secret: request_token.secret,
+ oauth_callback_confirmed: request_token.callback_confirmed?.to_s
+ }
+ end
+
+ def authorize_url(request_token, redirect_uri)
+ request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash)
+
+ if request_token.callback_confirmed?
+ request_token.authorize_url
+ else
+ request_token.authorize_url(oauth_callback: redirect_uri)
+ end
+ end
+
+ def get_token(request_token, oauth_verifier, redirect_uri)
+ request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash)
+
+ if request_token.callback_confirmed?
+ request_token.get_access_token(oauth_verifier: oauth_verifier)
+ else
+ request_token.get_access_token(oauth_callback: redirect_uri)
+ end
+ end
+
+ def user
+ JSON.parse(api.get("/api/1.0/user").body)
+ end
+
+ def issues(project_identifier)
+ JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues").body)
+ end
+
+ def issue_comments(project_identifier, issue_id)
+ JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body)
+ end
+
+ def project(project_identifier)
+ JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}").body)
+ end
+
+ def find_deploy_key(project_identifier, key)
+ JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find do |deploy_key|
+ deploy_key["key"].chomp == key.chomp
+ end
+ end
+
+ def add_deploy_key(project_identifier, key)
+ deploy_key = find_deploy_key(project_identifier, key)
+ return if deploy_key
+
+ JSON.parse(api.post("/api/1.0/repositories/#{project_identifier}/deploy-keys", key: key, label: "GitLab import key").body)
+ end
+
+ def delete_deploy_key(project_identifier, key)
+ deploy_key = find_deploy_key(project_identifier, key)
+ return unless deploy_key
+
+ api.delete("/api/1.0/repositories/#{project_identifier}/deploy-keys/#{deploy_key["pk"]}").code == "204"
+ end
+
+ def projects
+ JSON.parse(api.get("/api/1.0/user/repositories").body).select { |repo| repo["scm"] == "git" }
+ end
+
+ private
+
+ def config
+ Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"}
+ end
+
+ def bitbucket_options
+ OmniAuth::Strategies::Bitbucket.default_options[:client_options].symbolize_keys
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
new file mode 100644
index 0000000000..42c93707ca
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -0,0 +1,52 @@
+module Gitlab
+ module BitbucketImport
+ class Importer
+ attr_reader :project, :client
+
+ def initialize(project)
+ @project = project
+ @client = Client.new(project.creator.bitbucket_access_token, project.creator.bitbucket_access_token_secret)
+ @formatter = Gitlab::ImportFormatter.new
+ end
+
+ def execute
+ project_identifier = project.import_source
+
+ return true unless client.project(project_identifier)["has_issues"]
+
+ #Issues && Comments
+ issues = client.issues(project_identifier)
+
+ issues["issues"].each do |issue|
+ body = @formatter.author_line(issue["reported_by"]["username"], issue["content"])
+
+ comments = client.issue_comments(project_identifier, issue["local_id"])
+
+ if comments.any?
+ body += @formatter.comments_header
+ end
+
+ comments.each do |comment|
+ body += @formatter.comment(comment["author_info"]["username"], comment["utc_created_on"], comment["content"])
+ end
+
+ project.issues.create!(
+ description: body,
+ title: issue["title"],
+ state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened',
+ author_id: gl_user_id(project, issue["reported_by"]["username"])
+ )
+ end
+
+ true
+ end
+
+ private
+
+ def gl_user_id(project, bitbucket_id)
+ user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
+ (user && user.id) || project.creator_id
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/key_adder.rb b/lib/gitlab/bitbucket_import/key_adder.rb
new file mode 100644
index 0000000000..9931aa7e02
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/key_adder.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module BitbucketImport
+ class KeyAdder
+ attr_reader :repo, :current_user, :client
+
+ def initialize(repo, current_user)
+ @repo, @current_user = repo, current_user
+ @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret)
+ end
+
+ def execute
+ return false unless BitbucketImport.public_key.present?
+
+ project_identifier = "#{repo["owner"]}/#{repo["slug"]}"
+ client.add_deploy_key(project_identifier, BitbucketImport.public_key)
+
+ true
+ rescue
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb
new file mode 100644
index 0000000000..1a24a86fc3
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/key_deleter.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module BitbucketImport
+ class KeyDeleter
+ attr_reader :project, :current_user, :client
+
+ def initialize(project)
+ @project = project
+ @current_user = project.creator
+ @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret)
+ end
+
+ def execute
+ return false unless BitbucketImport.public_key.present?
+
+ client.delete_deploy_key(project.import_source, BitbucketImport.public_key)
+
+ true
+ rescue
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb
new file mode 100644
index 0000000000..54420e62c9
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/project_creator.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module BitbucketImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user
+
+ def initialize(repo, namespace, current_user)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ end
+
+ def execute
+ ::Projects::CreateService.new(current_user,
+ name: repo["name"],
+ path: repo["slug"],
+ description: repo["description"],
+ namespace_id: namespace.id,
+ visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC,
+ import_type: "bitbucket",
+ import_source: "#{repo["owner"]}/#{repo["slug"]}",
+ import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git"
+ ).execute
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb
index 6bc2c3b487..43145e0ee1 100644
--- a/lib/gitlab/blacklist.rb
+++ b/lib/gitlab/blacklist.rb
@@ -3,7 +3,32 @@ module Gitlab
extend self
def path
- %w(admin dashboard files groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes)
+ %w(
+ admin
+ dashboard
+ files
+ groups
+ help
+ profile
+ projects
+ search
+ public
+ assets
+ u
+ s
+ teams
+ merge_requests
+ issues
+ users
+ snippets
+ services
+ repository
+ hooks
+ notes
+ unsubscribes
+ all
+ ci
+ )
end
end
end
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index 90f1370c20..ab184d95c0 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -1,16 +1,20 @@
module Gitlab
- module ClosingIssueExtractor
+ class ClosingIssueExtractor
ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
- def self.closed_by_message_in_project(message, project)
- md = ISSUE_CLOSING_REGEX.match(message)
- if md
- extractor = Gitlab::ReferenceExtractor.new
- extractor.analyze(md[0])
- extractor.issues_for(project)
- else
- []
- end
+ def initialize(project, current_user = nil)
+ @extractor = Gitlab::ReferenceExtractor.new(project, current_user)
+ end
+
+ def closed_by_message(message)
+ return [] if message.nil?
+
+ closing_statements = message.scan(ISSUE_CLOSING_REGEX).
+ map { |ref| ref[0] }.join(" ")
+
+ @extractor.analyze(closing_statements)
+
+ @extractor.issues
end
end
end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
new file mode 100644
index 0000000000..3fd0823df0
--- /dev/null
+++ b/lib/gitlab/contributions_calendar.rb
@@ -0,0 +1,56 @@
+module Gitlab
+ class ContributionsCalendar
+ attr_reader :timestamps, :projects, :user
+
+ def initialize(projects, user)
+ @projects = projects
+ @user = user
+ end
+
+ def timestamps
+ return @timestamps if @timestamps.present?
+
+ @timestamps = {}
+ date_from = 1.year.ago
+ date_to = Date.today
+
+ events = Event.reorder(nil).contributions.where(author_id: user.id).
+ where("created_at > ?", date_from).where(project_id: projects).
+ group('date(created_at)').
+ select('date(created_at), count(id) as total_amount').
+ map(&:attributes)
+
+ dates = (1.year.ago.to_date..(Date.today + 1.day)).to_a
+
+ dates.each do |date|
+ date_id = date.to_time.to_i.to_s
+ @timestamps[date_id] = 0
+ day_events = events.find { |day_events| day_events["date"] == date }
+
+ if day_events
+ @timestamps[date_id] = day_events["total_amount"]
+ end
+ end
+
+ @timestamps
+ end
+
+ def events_by_date(date)
+ events = Event.contributions.where(author_id: user.id).
+ where("created_at > ? AND created_at < ?", date.beginning_of_day, date.end_of_day).
+ where(project_id: projects)
+
+ events.select do |event|
+ event.push? || event.issue? || event.merge_request?
+ end
+ end
+
+ def starting_year
+ (Time.now - 1.year).strftime("%Y")
+ end
+
+ def starting_month
+ Date.today.strftime("%m").to_i
+ end
+ end
+end
diff --git a/lib/gitlab/contributors.rb b/lib/gitlab/contributor.rb
similarity index 100%
rename from lib/gitlab/contributors.rb
rename to lib/gitlab/contributor.rb
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
new file mode 100644
index 0000000000..d8f696d247
--- /dev/null
+++ b/lib/gitlab/current_settings.rb
@@ -0,0 +1,28 @@
+module Gitlab
+ module CurrentSettings
+ def current_application_settings
+ key = :current_application_settings
+
+ RequestStore.store[key] ||= begin
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('application_settings')
+ ApplicationSetting.current || ApplicationSetting.create_from_defaults
+ else
+ fake_application_settings
+ end
+ end
+ end
+
+ def fake_application_settings
+ OpenStruct.new(
+ default_projects_limit: Settings.gitlab['default_projects_limit'],
+ default_branch_protection: Settings.gitlab['default_branch_protection'],
+ signup_enabled: Settings.gitlab['signup_enabled'],
+ signin_enabled: Settings.gitlab['signin_enabled'],
+ gravatar_enabled: Settings.gravatar['enabled'],
+ sign_in_text: Settings.extra['sign_in_text'],
+ restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
+ max_attachment_size: Settings.gitlab['max_attachment_size']
+ )
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
new file mode 100644
index 0000000000..4daf65331e
--- /dev/null
+++ b/lib/gitlab/diff/file.rb
@@ -0,0 +1,49 @@
+module Gitlab
+ module Diff
+ class File
+ attr_reader :diff
+
+ delegate :new_file, :deleted_file, :renamed_file,
+ :old_path, :new_path, to: :diff, prefix: false
+
+ def initialize(diff)
+ @diff = diff
+ end
+
+ # Array of Gitlab::DIff::Line objects
+ def diff_lines
+ @lines ||= parser.parse(raw_diff.lines)
+ end
+
+ def mode_changed?
+ !!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode)
+ end
+
+ def parser
+ Gitlab::Diff::Parser.new
+ end
+
+ def raw_diff
+ diff.diff.to_s
+ end
+
+ def next_line(index)
+ diff_lines[index + 1]
+ end
+
+ def prev_line(index)
+ if index > 0
+ diff_lines[index - 1]
+ end
+ end
+
+ def file_path
+ if diff.new_path.present?
+ diff.new_path
+ elsif diff.old_path.present?
+ diff.old_path
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
new file mode 100644
index 0000000000..8ac1b15e88
--- /dev/null
+++ b/lib/gitlab/diff/line.rb
@@ -0,0 +1,12 @@
+module Gitlab
+ module Diff
+ class Line
+ attr_reader :type, :text, :index, :old_pos, :new_pos
+
+ def initialize(text, type, index, old_pos, new_pos)
+ @text, @type, @index = text, type, index
+ @old_pos, @new_pos = old_pos, new_pos
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/line_code.rb b/lib/gitlab/diff/line_code.rb
new file mode 100644
index 0000000000..f3578ab3d3
--- /dev/null
+++ b/lib/gitlab/diff/line_code.rb
@@ -0,0 +1,9 @@
+module Gitlab
+ module Diff
+ class LineCode
+ def self.generate(file_path, new_line_position, old_line_position)
+ "#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
new file mode 100644
index 0000000000..c1d9520ddf
--- /dev/null
+++ b/lib/gitlab/diff/parser.rb
@@ -0,0 +1,81 @@
+module Gitlab
+ module Diff
+ class Parser
+ include Enumerable
+
+ def parse(lines)
+ @lines = lines
+ lines_obj = []
+ line_obj_index = 0
+ line_old = 1
+ line_new = 1
+ type = nil
+
+ lines_arr = ::Gitlab::InlineDiff.processing lines
+
+ lines_arr.each do |line|
+ raw_line = line.dup
+
+ next if filename?(line)
+
+ full_line = html_escape(line.gsub(/\n/, ''))
+ full_line = ::Gitlab::InlineDiff.replace_markers full_line
+
+ if line.match(/^@@ -/)
+ type = "match"
+
+ line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
+ line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
+
+ next if line_old <= 1 && line_new <= 1 #top of file
+ lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
+ line_obj_index += 1
+ next
+ else
+ type = identification_type(line)
+ lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
+ line_obj_index += 1
+ end
+
+
+ if line[0] == "+"
+ line_new += 1
+ elsif line[0] == "-"
+ line_old += 1
+ else
+ line_new += 1
+ line_old += 1
+ end
+ end
+
+ lines_obj
+ end
+
+ def empty?
+ @lines.empty?
+ end
+
+ private
+
+ def filename?(line)
+ line.start_with?('--- /dev/null', '+++ /dev/null', '--- a', '+++ b',
+ '--- /tmp/diffy', '+++ /tmp/diffy')
+ end
+
+ def identification_type(line)
+ if line[0] == "+"
+ "new"
+ elsif line[0] == "-"
+ "old"
+ else
+ nil
+ end
+ end
+
+ def html_escape(str)
+ replacements = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
+ str.gsub(/[&"'><]/, replacements)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff_parser.rb b/lib/gitlab/diff_parser.rb
deleted file mode 100644
index b244295027..0000000000
--- a/lib/gitlab/diff_parser.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-module Gitlab
- class DiffParser
- include Enumerable
-
- attr_reader :lines, :new_path
-
- def initialize(lines, new_path = '')
- @lines = lines
- @new_path = new_path
- end
-
- def each
- line_old = 1
- line_new = 1
- type = nil
-
- lines_arr = ::Gitlab::InlineDiff.processing lines
- lines_arr.each do |line|
- raw_line = line.dup
-
- next if filename?(line)
-
- full_line = html_escape(line.gsub(/\n/, ''))
- full_line = ::Gitlab::InlineDiff.replace_markers full_line
-
- if line.match(/^@@ -/)
- type = "match"
-
- line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
- line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
-
- next if line_old == 1 && line_new == 1 #top of file
- yield(full_line, type, nil, line_new, line_old)
- next
- else
- type = identification_type(line)
- line_code = generate_line_code(new_path, line_new, line_old)
- yield(full_line, type, line_code, line_new, line_old, raw_line)
- end
-
-
- if line[0] == "+"
- line_new += 1
- elsif line[0] == "-"
- line_old += 1
- else
- line_new += 1
- line_old += 1
- end
- end
- end
-
- def empty?
- @lines.empty?
- end
-
- private
-
- def filename?(line)
- line.start_with?('--- /dev/null', '+++ /dev/null', '--- a', '+++ b',
- '--- /tmp/diffy', '+++ /tmp/diffy')
- end
-
- def identification_type(line)
- if line[0] == "+"
- "new"
- elsif line[0] == "-"
- "old"
- else
- nil
- end
- end
-
- def generate_line_code(path, line_new, line_old)
- "#{Digest::SHA1.hexdigest(path)}_#{line_old}_#{line_new}"
- end
-
- def html_escape str
- replacements = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
- str.gsub(/[&"'><]/, replacements)
- end
- end
-end
diff --git a/lib/gitlab/force_push_check.rb b/lib/gitlab/force_push_check.rb
new file mode 100644
index 0000000000..fdb6a35c78
--- /dev/null
+++ b/lib/gitlab/force_push_check.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ class ForcePushCheck
+ def self.force_push?(project, oldrev, newrev)
+ return false if project.empty_repo?
+
+ # Created or deleted branch
+ if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
+ false
+ else
+ missed_refs, _ = Gitlab::Popen.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev}))
+ missed_refs.split("\n").size > 0
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
new file mode 100644
index 0000000000..0c350d7c67
--- /dev/null
+++ b/lib/gitlab/git.rb
@@ -0,0 +1,25 @@
+module Gitlab
+ module Git
+ BLANK_SHA = '0' * 40
+ TAG_REF_PREFIX = "refs/tags/"
+ BRANCH_REF_PREFIX = "refs/heads/"
+
+ class << self
+ def ref_name(ref)
+ ref.gsub(/\Arefs\/(tags|heads)\//, '')
+ end
+
+ def tag_ref?(ref)
+ ref.start_with?(TAG_REF_PREFIX)
+ end
+
+ def branch_ref?(ref)
+ ref.start_with?(BRANCH_REF_PREFIX)
+ end
+
+ def blank_ref?(ref)
+ ref == BLANK_SHA
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 38b3d82e2f..bc72b7528d 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -3,72 +3,196 @@ module Gitlab
DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
PUSH_COMMANDS = %w{ git-receive-pack }
- attr_reader :params, :project, :git_cmd, :user
+ attr_reader :actor, :project
- def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil, forced_push = false)
+ def initialize(actor, project)
+ @actor = actor
+ @project = project
+ end
+
+ def user
+ return @user if defined?(@user)
+
+ @user =
+ case actor
+ when User
+ actor
+ when DeployKey
+ nil
+ when Key
+ actor.user
+ end
+ end
+
+ def deploy_key
+ actor if actor.is_a?(DeployKey)
+ end
+
+ def can_push_to_branch?(ref)
+ return false unless user
+
+ if project.protected_branch?(ref) &&
+ !(project.developers_can_push_to_protected_branch?(ref) && project.team.developer?(user))
+ user.can?(:push_code_to_protected_branches, project)
+ else
+ user.can?(:push_code, project)
+ end
+ end
+
+ def can_read_project?
+ if user
+ user.can?(:read_project, project)
+ elsif deploy_key
+ deploy_key.projects.include?(project)
+ else
+ false
+ end
+ end
+
+ def check(cmd, changes = nil)
case cmd
when *DOWNLOAD_COMMANDS
- if actor.is_a? User
- download_allowed?(actor, project)
- elsif actor.is_a? DeployKey
- actor.projects.include?(project)
- elsif actor.is_a? Key
- download_allowed?(actor.user, project)
- else
- raise 'Wrong actor'
- end
+ download_access_check
when *PUSH_COMMANDS
- if actor.is_a? User
- push_allowed?(actor, project, ref, oldrev, newrev, forced_push)
- elsif actor.is_a? DeployKey
- # Deploy key not allowed to push
- return false
- elsif actor.is_a? Key
- push_allowed?(actor.user, project, ref, oldrev, newrev, forced_push)
- else
- raise 'Wrong actor'
+ push_access_check(changes)
+ else
+ build_status_object(false, "Wrong command")
+ end
+ end
+
+ def download_access_check
+ if user
+ user_download_access_check
+ elsif deploy_key
+ deploy_key_download_access_check
+ else
+ raise 'Wrong actor'
+ end
+ end
+
+ def push_access_check(changes)
+ if user
+ user_push_access_check(changes)
+ elsif deploy_key
+ build_status_object(false, "Deploy key not allowed to push")
+ else
+ raise 'Wrong actor'
+ end
+ end
+
+ def user_download_access_check
+ if user && user_allowed? && user.can?(:download_code, project)
+ build_status_object(true)
+ else
+ build_status_object(false, "You don't have access")
+ end
+ end
+
+ def deploy_key_download_access_check
+ if can_read_project?
+ build_status_object(true)
+ else
+ build_status_object(false, "Deploy key not allowed to access this project")
+ end
+ end
+
+ def user_push_access_check(changes)
+ unless user && user_allowed?
+ return build_status_object(false, "You don't have access")
+ end
+
+ if changes.blank?
+ return build_status_object(true)
+ end
+
+ unless project.repository.exists?
+ return build_status_object(false, "Repository does not exist")
+ end
+
+ changes = changes.lines if changes.kind_of?(String)
+
+ # Iterate over all changes to find if user allowed all of them to be applied
+ changes.map(&:strip).reject(&:blank?).each do |change|
+ status = change_access_check(change)
+ unless status.allowed?
+ # If user does not have access to make at least one change - cancel all push
+ return status
end
+ end
+
+ build_status_object(true)
+ end
+
+ def change_access_check(change)
+ oldrev, newrev, ref = change.split(' ')
+
+ action =
+ if project.protected_branch?(branch_name(ref))
+ protected_branch_action(oldrev, newrev, branch_name(ref))
+ elsif protected_tag?(tag_name(ref))
+ # Prevent any changes to existing git tag unless user has permissions
+ :admin_project
+ else
+ :push_code
+ end
+
+ if user.can?(action, project)
+ build_status_object(true)
else
- false
+ build_status_object(false, "You don't have permission")
end
end
- def download_allowed?(user, project)
- if user && user_allowed?(user)
- user.can?(:download_code, project)
- else
- false
- end
- end
-
- def push_allowed?(user, project, ref, oldrev, newrev, forced_push)
- if user && user_allowed?(user)
- action = if project.protected_branch?(ref)
- # we dont allow force push to protected branch
- if forced_push.to_s == 'true'
- :force_push_code_to_protected_branches
- # and we dont allow remove of protected branch
- elsif newrev =~ /0000000/
- :remove_protected_branches
- else
- :push_code_to_protected_branches
- end
- elsif project.repository && project.repository.tag_names.include?(ref)
- # Prevent any changes to existing git tag unless user has permissions
- :admin_project
- else
- :push_code
- end
- user.can?(action, project)
- else
- false
- end
+ def forced_push?(oldrev, newrev)
+ Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
end
private
- def user_allowed?(user)
+ def protected_branch_action(oldrev, newrev, branch_name)
+ # we dont allow force push to protected branch
+ if forced_push?(oldrev, newrev)
+ :force_push_code_to_protected_branches
+ elsif Gitlab::Git.blank_ref?(newrev)
+ # and we dont allow remove of protected branch
+ :remove_protected_branches
+ elsif project.developers_can_push_to_protected_branch?(branch_name)
+ :push_code
+ else
+ :push_code_to_protected_branches
+ end
+ end
+
+ def protected_tag?(tag_name)
+ project.repository.tag_names.include?(tag_name)
+ end
+
+ def user_allowed?
Gitlab::UserAccess.allowed?(user)
end
+
+ def branch_name(ref)
+ ref = ref.to_s
+ if Gitlab::Git.branch_ref?(ref)
+ Gitlab::Git.ref_name(ref)
+ else
+ nil
+ end
+ end
+
+ def tag_name(ref)
+ ref = ref.to_s
+ if Gitlab::Git.tag_ref?(ref)
+ Gitlab::Git.ref_name(ref)
+ else
+ nil
+ end
+ end
+
+ protected
+
+ def build_status_object(status, message = '')
+ GitAccessStatus.new(status, message)
+ end
end
end
diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb
new file mode 100644
index 0000000000..5a806ff6e0
--- /dev/null
+++ b/lib/gitlab/git_access_status.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ class GitAccessStatus
+ attr_accessor :status, :message
+ alias_method :allowed?, :status
+
+ def initialize(status, message = '')
+ @status = status
+ @message = message
+ end
+
+ def to_json
+ { status: @status, message: @message }.to_json
+ end
+ end
+end
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
new file mode 100644
index 0000000000..73d99b9620
--- /dev/null
+++ b/lib/gitlab/git_access_wiki.rb
@@ -0,0 +1,11 @@
+module Gitlab
+ class GitAccessWiki < GitAccess
+ def change_access_check(change)
+ if user.can?(:write_wiki, project)
+ build_status_object(true)
+ else
+ build_status_object(false, "You don't have access")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb
index fbfed205a0..9e02ccc0f4 100644
--- a/lib/gitlab/git_logger.rb
+++ b/lib/gitlab/git_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
class GitLogger < Gitlab::Logger
- def self.file_name
- 'githost.log'
+ def self.file_name_noext
+ 'githost'
end
def format_message(severity, timestamp, progname, msg)
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
new file mode 100644
index 0000000000..39d17def93
--- /dev/null
+++ b/lib/gitlab/git_ref_validator.rb
@@ -0,0 +1,12 @@
+module Gitlab
+ module GitRefValidator
+ extend self
+ # Validates a given name against the git reference specification
+ #
+ # Returns true for a valid reference name, false otherwise
+ def validate(ref_name)
+ Gitlab::Utils.system_silent(
+ %W(git check-ref-format refs/#{ref_name}))
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
new file mode 100644
index 0000000000..270cbcd9cc
--- /dev/null
+++ b/lib/gitlab/github_import/client.rb
@@ -0,0 +1,53 @@
+module Gitlab
+ module GithubImport
+ class Client
+ attr_reader :client, :api
+
+ def initialize(access_token)
+ @client = ::OAuth2::Client.new(
+ config.app_id,
+ config.app_secret,
+ github_options
+ )
+
+ if access_token
+ ::Octokit.auto_paginate = true
+ @api = ::Octokit::Client.new(access_token: access_token)
+ end
+ end
+
+ def authorize_url(redirect_uri)
+ client.auth_code.authorize_url({
+ redirect_uri: redirect_uri,
+ scope: "repo, user, user:email"
+ })
+ end
+
+ def get_token(code)
+ client.auth_code.get_token(code).token
+ end
+
+ def method_missing(method, *args, &block)
+ if api.respond_to?(method)
+ api.send(method, *args, &block)
+ else
+ super(method, *args, &block)
+ end
+ end
+
+ def respond_to?(method)
+ api.respond_to?(method) || super
+ end
+
+ private
+
+ def config
+ Gitlab.config.omniauth.providers.find{|provider| provider.name == "github"}
+ end
+
+ def github_options
+ OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
new file mode 100644
index 0000000000..23832b3233
--- /dev/null
+++ b/lib/gitlab/github_import/importer.rb
@@ -0,0 +1,46 @@
+module Gitlab
+ module GithubImport
+ class Importer
+ attr_reader :project, :client
+
+ def initialize(project)
+ @project = project
+ @client = Client.new(project.creator.github_access_token)
+ @formatter = Gitlab::ImportFormatter.new
+ end
+
+ def execute
+ #Issues && Comments
+ client.list_issues(project.import_source, state: :all).each do |issue|
+ if issue.pull_request.nil?
+
+ body = @formatter.author_line(issue.user.login, issue.body)
+
+ if issue.comments > 0
+ body += @formatter.comments_header
+
+ client.issue_comments(project.import_source, issue.number).each do |c|
+ body += @formatter.comment(c.user.login, c.created_at, c.body)
+ end
+ end
+
+ project.issues.create!(
+ description: body,
+ title: issue.title,
+ state: issue.state == 'closed' ? 'closed' : 'opened',
+ author_id: gl_user_id(project, issue.user.id)
+ )
+ end
+ end
+ end
+
+ private
+
+ def gl_user_id(project, github_id)
+ user = User.joins(:identities).
+ find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s)
+ (user && user.id) || project.creator_id
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb
new file mode 100644
index 0000000000..2723eec933
--- /dev/null
+++ b/lib/gitlab/github_import/project_creator.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module GithubImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user
+
+ def initialize(repo, namespace, current_user)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ end
+
+ def execute
+ ::Projects::CreateService.new(current_user,
+ name: repo.name,
+ path: repo.name,
+ description: repo.description,
+ namespace_id: namespace.id,
+ visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC,
+ import_type: "github",
+ import_source: repo.full_name,
+ import_url: repo.clone_url.sub("https://", "https://#{current_user.github_access_token}@")
+ ).execute
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb
new file mode 100644
index 0000000000..9c00896c91
--- /dev/null
+++ b/lib/gitlab/gitlab_import/client.rb
@@ -0,0 +1,82 @@
+module Gitlab
+ module GitlabImport
+ class Client
+ attr_reader :client, :api
+
+ PER_PAGE = 100
+
+ def initialize(access_token)
+ @client = ::OAuth2::Client.new(
+ config.app_id,
+ config.app_secret,
+ gitlab_options
+ )
+
+ if access_token
+ @api = OAuth2::AccessToken.from_hash(@client, access_token: access_token)
+ end
+ end
+
+ def authorize_url(redirect_uri)
+ client.auth_code.authorize_url({
+ redirect_uri: redirect_uri,
+ scope: "api"
+ })
+ end
+
+ def get_token(code, redirect_uri)
+ client.auth_code.get_token(code, redirect_uri: redirect_uri).token
+ end
+
+ def user
+ api.get("/api/v3/user").parsed
+ end
+
+ def issues(project_identifier)
+ lazy_page_iterator(PER_PAGE) do |page|
+ api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed
+ end
+ end
+
+ def issue_comments(project_identifier, issue_id)
+ lazy_page_iterator(PER_PAGE) do |page|
+ api.get("/api/v3/projects/#{project_identifier}/issues/#{issue_id}/notes?per_page=#{PER_PAGE}&page=#{page}").parsed
+ end
+ end
+
+ def project(id)
+ api.get("/api/v3/projects/#{id}").parsed
+ end
+
+ def projects
+ lazy_page_iterator(PER_PAGE) do |page|
+ api.get("/api/v3/projects?per_page=#{PER_PAGE}&page=#{page}").parsed
+ end
+ end
+
+ private
+
+ def lazy_page_iterator(per_page)
+ Enumerator.new do |y|
+ page = 1
+ loop do
+ items = yield(page)
+ items.each do |item|
+ y << item
+ end
+ break if items.empty? || items.size < per_page
+ page += 1
+ end
+ end
+ end
+
+ def config
+ Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"}
+ end
+
+ def gitlab_options
+ OmniAuth::Strategies::GitLab.default_options[:client_options].symbolize_keys
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
new file mode 100644
index 0000000000..c5304a0699
--- /dev/null
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -0,0 +1,50 @@
+module Gitlab
+ module GitlabImport
+ class Importer
+ attr_reader :project, :client
+
+ def initialize(project)
+ @project = project
+ @client = Client.new(project.creator.gitlab_access_token)
+ @formatter = Gitlab::ImportFormatter.new
+ end
+
+ def execute
+ project_identifier = URI.encode(project.import_source, '/')
+
+ #Issues && Comments
+ issues = client.issues(project_identifier)
+
+ issues.each do |issue|
+ body = @formatter.author_line(issue["author"]["name"], issue["description"])
+
+ comments = client.issue_comments(project_identifier, issue["id"])
+
+ if comments.any?
+ body += @formatter.comments_header
+ end
+
+ comments.each do |comment|
+ body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"])
+ end
+
+ project.issues.create!(
+ description: body,
+ title: issue["title"],
+ state: issue["state"],
+ author_id: gl_user_id(project, issue["author"]["id"])
+ )
+ end
+
+ true
+ end
+
+ private
+
+ def gl_user_id(project, gitlab_id)
+ user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'gitlab'", gitlab_id.to_s)
+ (user && user.id) || project.creator_id
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb
new file mode 100644
index 0000000000..f0d7141bf5
--- /dev/null
+++ b/lib/gitlab/gitlab_import/project_creator.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module GitlabImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user
+
+ def initialize(repo, namespace, current_user)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ end
+
+ def execute
+ ::Projects::CreateService.new(current_user,
+ name: repo["name"],
+ path: repo["path"],
+ description: repo["description"],
+ namespace_id: namespace.id,
+ visibility_level: repo["visibility_level"],
+ import_type: "gitlab",
+ import_source: repo["path_with_namespace"],
+ import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{current_user.gitlab_access_token}@")
+ ).execute
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb
new file mode 100644
index 0000000000..8cdc3d4afa
--- /dev/null
+++ b/lib/gitlab/gitorious_import/client.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module GitoriousImport
+ GITORIOUS_HOST = "https://gitorious.org"
+
+ class Client
+ attr_reader :repo_list
+
+ def initialize(repo_list)
+ @repo_list = repo_list
+ end
+
+ def authorize_url(redirect_uri)
+ "#{GITORIOUS_HOST}/gitlab-import?callback_url=#{redirect_uri}"
+ end
+
+ def repos
+ @repos ||= repo_names.map { |full_name| Repository.new(full_name) }
+ end
+
+ def repo(id)
+ repos.find { |repo| repo.id == id }
+ end
+
+ private
+
+ def repo_names
+ repo_list.to_s.split(',').map(&:strip).reject(&:blank?)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb
new file mode 100644
index 0000000000..cc9a91c91f
--- /dev/null
+++ b/lib/gitlab/gitorious_import/project_creator.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module GitoriousImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user
+
+ def initialize(repo, namespace, current_user)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ end
+
+ def execute
+ ::Projects::CreateService.new(current_user,
+ name: repo.name,
+ path: repo.path,
+ description: repo.description,
+ namespace_id: namespace.id,
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC,
+ import_type: "gitorious",
+ import_source: repo.full_name,
+ import_url: repo.import_url
+ ).execute
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitorious_import/repository.rb b/lib/gitlab/gitorious_import/repository.rb
new file mode 100644
index 0000000000..f702797dc6
--- /dev/null
+++ b/lib/gitlab/gitorious_import/repository.rb
@@ -0,0 +1,37 @@
+module Gitlab
+ module GitoriousImport
+ GITORIOUS_HOST = "https://gitorious.org"
+
+ Repository = Struct.new(:full_name) do
+ def id
+ Digest::SHA1.hexdigest(full_name)
+ end
+
+ def namespace
+ segments.first
+ end
+
+ def path
+ segments.last
+ end
+
+ def name
+ path.titleize
+ end
+
+ def description
+ ""
+ end
+
+ def import_url
+ "#{GITORIOUS_HOST}/#{full_name}.git"
+ end
+
+ private
+
+ def segments
+ full_name.split('/')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/google_code_import/client.rb b/lib/gitlab/google_code_import/client.rb
new file mode 100644
index 0000000000..02f31e45f8
--- /dev/null
+++ b/lib/gitlab/google_code_import/client.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module GoogleCodeImport
+ class Client
+ attr_reader :raw_data
+
+ def self.mask_email(author)
+ parts = author.split("@", 2)
+ parts[0] = "#{parts[0][0...-3]}..."
+ parts.join("@")
+ end
+
+ def initialize(raw_data)
+ @raw_data = raw_data
+ end
+
+ def valid?
+ raw_data.is_a?(Hash) && raw_data["kind"] == "projecthosting#user" && raw_data.has_key?("projects")
+ end
+
+ def repos
+ @repos ||= raw_data["projects"].map { |raw_repo| GoogleCodeImport::Repository.new(raw_repo) }.select(&:git?)
+ end
+
+ def repo(id)
+ repos.find { |repo| repo.id == id }
+ end
+
+ def user_map
+ user_map = Hash.new { |hash, user| hash[user] = self.class.mask_email(user) }
+
+ repos.each do |repo|
+ next unless repo.valid? && repo.issues
+
+ repo.issues.each do |raw_issue|
+ # Touching is enough to add the entry and masked email.
+ user_map[raw_issue["author"]["name"]]
+
+ raw_issue["comments"]["items"].each do |raw_comment|
+ user_map[raw_comment["author"]["name"]]
+ end
+ end
+ end
+
+ Hash[user_map.sort]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
new file mode 100644
index 0000000000..70bfe05977
--- /dev/null
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -0,0 +1,377 @@
+module Gitlab
+ module GoogleCodeImport
+ class Importer
+ attr_reader :project, :repo
+
+ def initialize(project)
+ @project = project
+
+ import_data = project.import_data.try(:data)
+ repo_data = import_data["repo"] if import_data
+ @repo = GoogleCodeImport::Repository.new(repo_data)
+
+ @closed_statuses = []
+ @known_labels = Set.new
+ end
+
+ def execute
+ return true unless repo.valid?
+
+ import_status_labels
+
+ import_labels
+
+ import_issues
+
+ true
+ end
+
+ private
+
+ def user_map
+ @user_map ||= begin
+ user_map = Hash.new do |hash, user|
+ # Replace ... by \.\.\., so `johnsm...@gmail.com` isn't autolinked.
+ Client.mask_email(user).sub("...", "\\.\\.\\.")
+ end
+
+ import_data = project.import_data.try(:data)
+ stored_user_map = import_data["user_map"] if import_data
+ user_map.update(stored_user_map) if stored_user_map
+
+ user_map
+ end
+ end
+
+ def import_status_labels
+ repo.raw_data["issuesConfig"]["statuses"].each do |status|
+ closed = !status["meansOpen"]
+ @closed_statuses << status["status"] if closed
+
+ name = nice_status_name(status["status"])
+ create_label(name)
+ @known_labels << name
+ end
+ end
+
+ def import_labels
+ repo.raw_data["issuesConfig"]["labels"].each do |label|
+ name = nice_label_name(label["label"])
+ create_label(name)
+ @known_labels << name
+ end
+ end
+
+ def import_issues
+ return unless repo.issues
+
+ while raw_issue = repo.issues.shift
+ author = user_map[raw_issue["author"]["name"]]
+ date = DateTime.parse(raw_issue["published"]).to_formatted_s(:long)
+
+ comments = raw_issue["comments"]["items"]
+ issue_comment = comments.shift
+
+ content = format_content(issue_comment["content"])
+ attachments = format_attachments(raw_issue["id"], 0, issue_comment["attachments"])
+
+ body = format_issue_body(author, date, content, attachments)
+
+ labels = []
+ raw_issue["labels"].each do |label|
+ name = nice_label_name(label)
+ labels << name
+
+ unless @known_labels.include?(name)
+ create_label(name)
+ @known_labels << name
+ end
+ end
+ labels << nice_status_name(raw_issue["status"])
+
+ assignee_id = nil
+ if raw_issue.has_key?("owner")
+ username = user_map[raw_issue["owner"]["name"]]
+
+ if username.start_with?("@")
+ username = username[1..-1]
+
+ if user = User.find_by(username: username)
+ assignee_id = user.id
+ end
+ end
+ end
+
+ issue = Issue.create!(
+ project_id: project.id,
+ title: raw_issue["title"],
+ description: body,
+ author_id: project.creator_id,
+ assignee_id: assignee_id,
+ state: raw_issue["state"] == "closed" ? "closed" : "opened"
+ )
+ issue.add_labels_by_names(labels)
+
+ if issue.iid != raw_issue["id"]
+ issue.update_attribute(:iid, raw_issue["id"])
+ end
+
+ import_issue_comments(issue, comments)
+ end
+ end
+
+ def import_issue_comments(issue, comments)
+ Note.transaction do
+ while raw_comment = comments.shift
+ next if raw_comment.has_key?("deletedBy")
+
+ content = format_content(raw_comment["content"])
+ updates = format_updates(raw_comment["updates"])
+ attachments = format_attachments(issue.iid, raw_comment["id"], raw_comment["attachments"])
+
+ next if content.blank? && updates.blank? && attachments.blank?
+
+ author = user_map[raw_comment["author"]["name"]]
+ date = DateTime.parse(raw_comment["published"]).to_formatted_s(:long)
+
+ body = format_issue_comment_body(
+ raw_comment["id"],
+ author,
+ date,
+ content,
+ updates,
+ attachments
+ )
+
+ # Needs to match order of `comment_columns` below.
+ Note.create!(
+ project_id: project.id,
+ noteable_type: "Issue",
+ noteable_id: issue.id,
+ author_id: project.creator_id,
+ note: body
+ )
+ end
+ end
+ end
+
+ def nice_label_color(name)
+ case name
+ when /\AComponent:/
+ "#fff39e"
+ when /\AOpSys:/
+ "#e2e2e2"
+ when /\AMilestone:/
+ "#fee3ff"
+
+ when *@closed_statuses.map { |s| nice_status_name(s) }
+ "#cfcfcf"
+ when "Status: New"
+ "#428bca"
+ when "Status: Accepted"
+ "#5cb85c"
+ when "Status: Started"
+ "#8e44ad"
+
+ when "Priority: Critical"
+ "#ffcfcf"
+ when "Priority: High"
+ "#deffcf"
+ when "Priority: Medium"
+ "#fff5cc"
+ when "Priority: Low"
+ "#cfe9ff"
+
+ when "Type: Defect"
+ "#d9534f"
+ when "Type: Enhancement"
+ "#44ad8e"
+ when "Type: Task"
+ "#4b6dd0"
+ when "Type: Review"
+ "#8e44ad"
+ when "Type: Other"
+ "#7f8c8d"
+ else
+ "#e2e2e2"
+ end
+ end
+
+ def nice_label_name(name)
+ name.sub("-", ": ")
+ end
+
+ def nice_status_name(name)
+ "Status: #{name}"
+ end
+
+ def linkify_issues(s)
+ s = s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2')
+ s = s.gsub(/([Cc]omment) #([0-9]+)/, '\1 \2')
+ s
+ end
+
+ def escape_for_markdown(s)
+ # No headings and lists
+ s = s.gsub(/^#/, "\\#")
+ s = s.gsub(/^-/, "\\-")
+
+ # No inline code
+ s = s.gsub("`", "\\`")
+
+ # Carriage returns make me sad
+ s = s.gsub("\r", "")
+
+ # Markdown ignores single newlines, but we need them as .
+ s = s.gsub("\n", " \n")
+
+ s
+ end
+
+ def create_label(name)
+ color = nice_label_color(name)
+ Label.create!(project_id: project.id, name: name, color: color)
+ end
+
+ def format_content(raw_content)
+ linkify_issues(escape_for_markdown(raw_content))
+ end
+
+ def format_updates(raw_updates)
+ updates = []
+
+ if raw_updates.has_key?("status")
+ updates << "*Status: #{raw_updates["status"]}*"
+ end
+
+ if raw_updates.has_key?("owner")
+ updates << "*Owner: #{user_map[raw_updates["owner"]]}*"
+ end
+
+ if raw_updates.has_key?("cc")
+ cc = raw_updates["cc"].map do |l|
+ deleted = l.start_with?("-")
+ l = l[1..-1] if deleted
+ l = user_map[l]
+ l = "~~#{l}~~" if deleted
+ l
+ end
+
+ updates << "*Cc: #{cc.join(", ")}*"
+ end
+
+ if raw_updates.has_key?("labels")
+ labels = raw_updates["labels"].map do |l|
+ deleted = l.start_with?("-")
+ l = l[1..-1] if deleted
+ l = nice_label_name(l)
+ l = "~~#{l}~~" if deleted
+ l
+ end
+
+ updates << "*Labels: #{labels.join(", ")}*"
+ end
+
+ if raw_updates.has_key?("mergedInto")
+ updates << "*Merged into: ##{raw_updates["mergedInto"]}*"
+ end
+
+ if raw_updates.has_key?("blockedOn")
+ blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on|
+ name, id = raw_blocked_on.split(":", 2)
+
+ deleted = name.start_with?("-")
+ name = name[1..-1] if deleted
+
+ text =
+ if name == project.import_source
+ "##{id}"
+ else
+ "#{project.namespace.path}/#{name}##{id}"
+ end
+ text = "~~#{text}~~" if deleted
+ text
+ end
+ updates << "*Blocked on: #{blocked_ons.join(", ")}*"
+ end
+
+ if raw_updates.has_key?("blocking")
+ blockings = raw_updates["blocking"].map do |raw_blocked_on|
+ name, id = raw_blocked_on.split(":", 2)
+
+ deleted = name.start_with?("-")
+ name = name[1..-1] if deleted
+
+ text =
+ if name == project.import_source
+ "##{id}"
+ else
+ "#{project.namespace.path}/#{name}##{id}"
+ end
+ text = "~~#{text}~~" if deleted
+ text
+ end
+ updates << "*Blocking: #{blockings.join(", ")}*"
+ end
+
+ updates
+ end
+
+ def format_attachments(issue_id, comment_id, raw_attachments)
+ return [] unless raw_attachments
+
+ raw_attachments.map do |attachment|
+ next if attachment["isDeleted"]
+
+ filename = attachment["fileName"]
+ link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}"
+
+ text = "[#{filename}](#{link})"
+ text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/
+ text
+ end.compact
+ end
+
+ def format_issue_comment_body(id, author, date, content, updates, attachments)
+ body = []
+ body << "*Comment #{id} by #{author} on #{date}*"
+ body << "---"
+
+ if content.blank?
+ content = "*(No comment has been entered for this change)*"
+ end
+ body << content
+
+ if updates.any?
+ body << "---"
+ body += updates
+ end
+
+ if attachments.any?
+ body << "---"
+ body += attachments
+ end
+
+ body.join("\n\n")
+ end
+
+ def format_issue_body(author, date, content, attachments)
+ body = []
+ body << "*By #{author} on #{date} (imported from Google Code)*"
+ body << "---"
+
+ if content.blank?
+ content = "*(No description has been entered for this issue)*"
+ end
+ body << content
+
+ if attachments.any?
+ body << "---"
+ body += attachments
+ end
+
+ body.join("\n\n")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb
new file mode 100644
index 0000000000..0cfeaf9d61
--- /dev/null
+++ b/lib/gitlab/google_code_import/project_creator.rb
@@ -0,0 +1,37 @@
+module Gitlab
+ module GoogleCodeImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user, :user_map
+
+ def initialize(repo, namespace, current_user, user_map = nil)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ @user_map = user_map
+ end
+
+ def execute
+ project = ::Projects::CreateService.new(current_user,
+ name: repo.name,
+ path: repo.name,
+ description: repo.summary,
+ namespace: namespace,
+ creator: current_user,
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC,
+ import_type: "google_code",
+ import_source: repo.name,
+ import_url: repo.import_url
+ ).execute
+
+ import_data = project.create_import_data(
+ data: {
+ "repo" => repo.raw_data,
+ "user_map" => user_map
+ }
+ )
+
+ project
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/google_code_import/repository.rb b/lib/gitlab/google_code_import/repository.rb
new file mode 100644
index 0000000000..ad33fc2cad
--- /dev/null
+++ b/lib/gitlab/google_code_import/repository.rb
@@ -0,0 +1,43 @@
+module Gitlab
+ module GoogleCodeImport
+ class Repository
+ attr_accessor :raw_data
+
+ def initialize(raw_data)
+ @raw_data = raw_data
+ end
+
+ def valid?
+ raw_data.is_a?(Hash) && raw_data["kind"] == "projecthosting#project"
+ end
+
+ def id
+ raw_data["externalId"]
+ end
+
+ def name
+ raw_data["name"]
+ end
+
+ def summary
+ raw_data["summary"]
+ end
+
+ def description
+ raw_data["description"]
+ end
+
+ def git?
+ raw_data["versionControlSystem"] == "git"
+ end
+
+ def import_url
+ raw_data["repositoryUrls"].first
+ end
+
+ def issues
+ raw_data["issues"] && raw_data["issues"]["items"]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb
new file mode 100644
index 0000000000..2122339d2d
--- /dev/null
+++ b/lib/gitlab/graphs/commits.rb
@@ -0,0 +1,49 @@
+module Gitlab
+ module Graphs
+ class Commits
+ attr_reader :commits, :start_date, :end_date, :duration,
+ :commits_per_week_days, :commits_per_time, :commits_per_month
+
+ def initialize(commits)
+ @commits = commits
+ @start_date = commits.last.committed_date.to_date
+ @end_date = commits.first.committed_date.to_date
+ @duration = (@end_date - @start_date).to_i
+
+ collect_data
+ end
+
+ def authors
+ @authors ||= @commits.map(&:author_email).uniq.size
+ end
+
+ def commit_per_day
+ @commit_per_day ||= (@commits.size.to_f / @duration).round(1)
+ end
+
+ def collect_data
+ @commits_per_week_days = {}
+ Date::DAYNAMES.each { |day| @commits_per_week_days[day] = 0 }
+
+ @commits_per_time = {}
+ (0..23).to_a.each { |hour| @commits_per_time[hour] = 0 }
+
+ @commits_per_month = {}
+ (1..31).to_a.each { |day| @commits_per_month[day] = 0 }
+
+ @commits.each do |commit|
+ hour = commit.committed_date.strftime('%k').to_i
+ day_of_month = commit.committed_date.strftime('%e').to_i
+ weekday = commit.committed_date.strftime('%A')
+
+ @commits_per_week_days[weekday] ||= 0
+ @commits_per_week_days[weekday] += 1
+ @commits_per_time[hour] ||= 0
+ @commits_per_time[hour] += 1
+ @commits_per_month[day_of_month] ||= 0
+ @commits_per_month[day_of_month] += 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb
new file mode 100644
index 0000000000..72e041a90b
--- /dev/null
+++ b/lib/gitlab/import_formatter.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ class ImportFormatter
+ def comment(author, date, body)
+ "\n\n*By #{author} on #{date}*\n\n#{body}"
+ end
+
+ def comments_header
+ "\n\n\n**Imported comments:**\n"
+ end
+
+ def author_line(author, body)
+ "*Created by: #{author}*\n\n#{body}"
+ end
+ end
+end
diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb
index 89c8e0680c..3517ecdf5c 100644
--- a/lib/gitlab/inline_diff.rb
+++ b/lib/gitlab/inline_diff.rb
@@ -5,7 +5,7 @@ module Gitlab
START = "#!idiff-start!#"
FINISH = "#!idiff-finish!#"
- def processing diff_arr
+ def processing(diff_arr)
indexes = _indexes_of_changed_lines diff_arr
indexes.each do |index|
@@ -52,7 +52,7 @@ module Gitlab
diff_arr
end
- def _indexes_of_changed_lines diff_arr
+ def _indexes_of_changed_lines(diff_arr)
chain_of_first_symbols = ""
diff_arr.each_with_index do |line, i|
chain_of_first_symbols += line[0]
@@ -68,7 +68,7 @@ module Gitlab
indexes
end
- def replace_markers line
+ def replace_markers(line)
line.gsub!(START, "")
line.gsub!(FINISH, "")
line
diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb
index 0d34976736..1bec608829 100644
--- a/lib/gitlab/issues_labels.rb
+++ b/lib/gitlab/issues_labels.rb
@@ -15,7 +15,6 @@ module Gitlab
{ title: "support", color: yellow },
{ title: "discussion", color: blue },
{ title: "suggestion", color: blue },
- { title: "feature", color: green },
{ title: "enhancement", color: green }
]
diff --git a/lib/gitlab/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb
new file mode 100644
index 0000000000..baf52ff750
--- /dev/null
+++ b/lib/gitlab/key_fingerprint.rb
@@ -0,0 +1,55 @@
+module Gitlab
+ class KeyFingerprint
+ include Gitlab::Popen
+
+ attr_accessor :key
+
+ def initialize(key)
+ @key = key
+ end
+
+ def fingerprint
+ cmd_status = 0
+ cmd_output = ''
+
+ Tempfile.open('gitlab_key_file') do |file|
+ file.puts key
+ file.rewind
+
+ cmd = []
+ cmd.push *%W(ssh-keygen)
+ cmd.push *%W(-E md5) if explicit_fingerprint_algorithm?
+ cmd.push *%W(-lf #{file.path})
+
+ cmd_output, cmd_status = popen(cmd, '/tmp')
+ end
+
+ return nil unless cmd_status.zero?
+
+ # 16 hex bytes separated by ':', optionally starting with "MD5:"
+ fingerprint_matches = cmd_output.match(/(MD5:)?(?(\h{2}:){15}\h{2})/)
+ return nil unless fingerprint_matches
+
+ fingerprint_matches[:fingerprint]
+ end
+
+ private
+
+ def explicit_fingerprint_algorithm?
+ # OpenSSH 6.8 introduces a new default output format for fingerprints.
+ # Check the version and decide which command to use.
+
+ version_output, version_status = popen(%W(ssh -V))
+ return false unless version_status.zero?
+
+ version_matches = version_output.match(/OpenSSH_(?\d+)\.(?\d+)/)
+ return false unless version_matches
+
+ version_info = Gitlab::VersionInfo.new(version_matches[:major].to_i, version_matches[:minor].to_i)
+
+ required_version_info = Gitlab::VersionInfo.new(6, 8)
+
+ version_info >= required_version_info
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index 62709a1294..960fb3849b 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -1,18 +1,21 @@
+# LDAP authorization model
+#
+# * Check if we are allowed access (not blocked)
+#
module Gitlab
module LDAP
class Access
- attr_reader :adapter
+ attr_reader :adapter, :provider, :user
- def self.open(&block)
- Gitlab::LDAP::Adapter.open do |adapter|
- block.call(self.new(adapter))
+ def self.open(user, &block)
+ Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter|
+ block.call(self.new(user, adapter))
end
end
def self.allowed?(user)
- self.open do |access|
- if access.allowed?(user)
- # GitLab EE LDAP code goes here
+ self.open(user) do |access|
+ if access.allowed?
user.last_credential_check_at = Time.now
user.save
true
@@ -22,19 +25,38 @@ module Gitlab
end
end
- def initialize(adapter=nil)
+ def initialize(user, adapter=nil)
@adapter = adapter
+ @user = user
+ @provider = user.ldap_identity.provider
end
- def allowed?(user)
- if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
- !Gitlab::LDAP::Person.active_directory_disabled?(user.extern_uid, adapter)
+ def allowed?
+ if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
+ return true unless ldap_config.active_directory
+
+ # Block user in GitLab if he/she was blocked in AD
+ if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
+ user.block unless user.blocked?
+ false
+ else
+ user.activate if user.blocked?
+ true
+ end
else
false
end
rescue
false
end
+
+ def adapter
+ @adapter ||= Gitlab::LDAP::Adapter.new(provider)
+ end
+
+ def ldap_config
+ Gitlab::LDAP::Config.new(provider)
+ end
end
end
end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index ca239bea88..577a890a7d 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -1,55 +1,28 @@
module Gitlab
module LDAP
class Adapter
- attr_reader :ldap
+ attr_reader :provider, :ldap
- def self.open(&block)
- Net::LDAP.open(adapter_options) do |ldap|
- block.call(self.new(ldap))
+ def self.open(provider, &block)
+ Net::LDAP.open(config(provider).adapter_options) do |ldap|
+ block.call(self.new(provider, ldap))
end
end
- def self.config
- Gitlab.config.ldap
+ def self.config(provider)
+ Gitlab::LDAP::Config.new(provider)
end
- def self.adapter_options
- encryption =
- case config['method'].to_s
- when 'ssl'
- :simple_tls
- when 'tls'
- :start_tls
- else
- nil
- end
-
- options = {
- host: config['host'],
- port: config['port'],
- encryption: encryption
- }
-
- auth_options = {
- auth: {
- method: :simple,
- username: config['bind_dn'],
- password: config['password']
- }
- }
-
- if config['password'] || config['bind_dn']
- options.merge!(auth_options)
- end
- options
+ def initialize(provider, ldap=nil)
+ @provider = provider
+ @ldap = ldap || Net::LDAP.new(config.adapter_options)
end
-
- def initialize(ldap=nil)
- @ldap = ldap || Net::LDAP.new(self.class.adapter_options)
+ def config
+ Gitlab::LDAP::Config.new(provider)
end
- def users(field, value)
+ def users(field, value, limit = nil)
if field.to_sym == :dn
options = {
base: value,
@@ -57,13 +30,13 @@ module Gitlab
}
else
options = {
- base: config['base'],
+ base: config.base,
filter: Net::LDAP::Filter.eq(field, value)
}
end
- if config['user_filter'].present?
- user_filter = Net::LDAP::Filter.construct(config['user_filter'])
+ if config.user_filter.present?
+ user_filter = Net::LDAP::Filter.construct(config.user_filter)
options[:filter] = if options[:filter]
Net::LDAP::Filter.join(options[:filter], user_filter)
@@ -72,12 +45,16 @@ module Gitlab
end
end
+ if limit.present?
+ options.merge!(size: limit)
+ end
+
entries = ldap_search(options).select do |entry|
entry.respond_to? config.uid
end
entries.map do |entry|
- Gitlab::LDAP::Person.new(entry)
+ Gitlab::LDAP::Person.new(entry, provider)
end
end
@@ -86,7 +63,10 @@ module Gitlab
end
def dn_matches_filter?(dn, filter)
- ldap_search(base: dn, filter: filter, scope: Net::LDAP::SearchScope_BaseObject, attributes: %w{dn}).any?
+ ldap_search(base: dn,
+ filter: filter,
+ scope: Net::LDAP::SearchScope_BaseObject,
+ attributes: %w{dn}).any?
end
def ldap_search(*args)
@@ -104,12 +84,6 @@ module Gitlab
results
end
end
-
- private
-
- def config
- @config ||= self.class.config
- end
end
end
end
diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb
new file mode 100644
index 0000000000..649cf3194b
--- /dev/null
+++ b/lib/gitlab/ldap/authentication.rb
@@ -0,0 +1,71 @@
+# This calls helps to authenticate to LDAP by providing username and password
+#
+# Since multiple LDAP servers are supported, it will loop through all of them
+# until a valid bind is found
+#
+
+module Gitlab
+ module LDAP
+ class Authentication
+ def self.login(login, password)
+ return unless Gitlab::LDAP::Config.enabled?
+ return unless login.present? && password.present?
+
+ auth = nil
+ # loop through providers until valid bind
+ providers.find do |provider|
+ auth = new(provider)
+ auth.login(login, password) # true will exit the loop
+ end
+
+ # If (login, password) was invalid for all providers, the value of auth is now the last
+ # Gitlab::LDAP::Authentication instance we tried.
+ auth.user
+ end
+
+ def self.providers
+ Gitlab::LDAP::Config.providers
+ end
+
+ attr_accessor :provider, :ldap_user
+
+ def initialize(provider)
+ @provider = provider
+ end
+
+ def login(login, password)
+ @ldap_user = adapter.bind_as(
+ filter: user_filter(login),
+ size: 1,
+ password: password
+ )
+ end
+
+ def adapter
+ OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys)
+ end
+
+ def config
+ Gitlab::LDAP::Config.new(provider)
+ end
+
+ def user_filter(login)
+ filter = Net::LDAP::Filter.equals(config.uid, login)
+
+ # Apply LDAP user filter if present
+ if config.user_filter.present?
+ filter = Net::LDAP::Filter.join(
+ filter,
+ Net::LDAP::Filter.construct(config.user_filter)
+ )
+ end
+ filter
+ end
+
+ def user
+ return nil unless ldap_user
+ Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
new file mode 100644
index 0000000000..d2ffa2e1fe
--- /dev/null
+++ b/lib/gitlab/ldap/config.rb
@@ -0,0 +1,122 @@
+# Load a specific server configuration
+module Gitlab
+ module LDAP
+ class Config
+ attr_accessor :provider, :options
+
+ def self.enabled?
+ Gitlab.config.ldap.enabled
+ end
+
+ def self.servers
+ Gitlab.config.ldap.servers.values
+ end
+
+ def self.providers
+ servers.map {|server| server['provider_name'] }
+ end
+
+ def self.valid_provider?(provider)
+ providers.include?(provider)
+ end
+
+ def self.invalid_provider(provider)
+ raise "Unknown provider (#{provider}). Available providers: #{providers}"
+ end
+
+ def initialize(provider)
+ if self.class.valid_provider?(provider)
+ @provider = provider
+ else
+ self.class.invalid_provider(provider)
+ end
+ @options = config_for(@provider) # Use @provider, not provider
+ end
+
+ def enabled?
+ base_config.enabled
+ end
+
+ def adapter_options
+ {
+ host: options['host'],
+ port: options['port'],
+ encryption: encryption
+ }.tap do |options|
+ options.merge!(auth_options) if has_auth?
+ end
+ end
+
+ def base
+ options['base']
+ end
+
+ def uid
+ options['uid']
+ end
+
+ def sync_ssh_keys?
+ sync_ssh_keys.present?
+ end
+
+ # The LDAP attribute in which the ssh keys are stored
+ def sync_ssh_keys
+ options['sync_ssh_keys']
+ end
+
+ def user_filter
+ options['user_filter']
+ end
+
+ def group_base
+ options['group_base']
+ end
+
+ def admin_group
+ options['admin_group']
+ end
+
+ def active_directory
+ options['active_directory']
+ end
+
+ def block_auto_created_users
+ options['block_auto_created_users']
+ end
+
+ protected
+ def base_config
+ Gitlab.config.ldap
+ end
+
+ def config_for(provider)
+ base_config.servers.values.find { |server| server['provider_name'] == provider }
+ end
+
+ def encryption
+ case options['method'].to_s
+ when 'ssl'
+ :simple_tls
+ when 'tls'
+ :start_tls
+ else
+ nil
+ end
+ end
+
+ def auth_options
+ {
+ auth: {
+ method: :simple,
+ username: options['bind_dn'],
+ password: options['password']
+ }
+ }
+ end
+
+ def has_auth?
+ options['password'] || options['bind_dn']
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb
index 9ad6618bd4..b81f3e8e8f 100644
--- a/lib/gitlab/ldap/person.rb
+++ b/lib/gitlab/ldap/person.rb
@@ -6,24 +6,25 @@ module Gitlab
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
- def self.find_by_uid(uid, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
- adapter.user(config.uid, uid)
+ attr_accessor :entry, :provider
+
+ def self.find_by_uid(uid, adapter)
+ uid = Net::LDAP::Filter.escape(uid)
+ adapter.user(adapter.config.uid, uid)
end
- def self.find_by_dn(dn, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
+ def self.find_by_dn(dn, adapter)
adapter.user('dn', dn)
end
- def self.active_directory_disabled?(dn, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
+ def self.disabled_via_active_directory?(dn, adapter)
adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
end
- def initialize(entry)
+ def initialize(entry, provider)
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry
+ @provider = provider
end
def name
@@ -38,6 +39,10 @@ module Gitlab
uid
end
+ def email
+ entry.try(:mail)
+ end
+
def dn
entry.dn
end
@@ -48,12 +53,8 @@ module Gitlab
@entry
end
- def adapter
- @adapter ||= Gitlab::LDAP::Adapter.new
- end
-
def config
- @config ||= Gitlab.config.ldap
+ @config ||= Gitlab::LDAP::Config.new(provider)
end
end
end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index be3fcc4f03..f7f3ba9ad7 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -1,4 +1,4 @@
-require 'gitlab/oauth/user'
+require 'gitlab/o_auth/user'
# LDAP extension for User model
#
@@ -10,103 +10,61 @@ module Gitlab
module LDAP
class User < Gitlab::OAuth::User
class << self
- def find_or_create(auth)
- @auth = auth
-
- if uid.blank? || email.blank? || username.blank?
- raise_error("Account must provide a dn, uid and email address")
- end
-
- user = find(auth)
-
- unless user
- # Look for user with same emails
- #
- # Possible cases:
- # * When user already has account and need to link their LDAP account.
- # * LDAP uid changed for user with same email and we need to update their uid
- #
- user = find_user(email)
-
- if user
- user.update_attributes(extern_uid: uid, provider: provider)
- log.info("(LDAP) Updating legacy LDAP user #{email} with extern_uid => #{uid}")
- else
- # Create a new user inside GitLab database
- # based on LDAP credentials
- #
- #
- user = create(auth)
- end
- end
-
- user
- end
-
- def find_user(email)
- user = model.find_by(email: email)
-
- # If no user found and allow_username_or_email_login is true
- # we look for user by extracting part of their email
- if !user && email && ldap_conf['allow_username_or_email_login']
- uname = email.partition('@').first
- # Strip apostrophes since they are disallowed as part of username
- username = uname.gsub("'", "")
- user = model.find_by(username: username)
- end
-
- user
- end
-
- def authenticate(login, password)
- # Check user against LDAP backend if user is not authenticated
- # Only check with valid login and password to prevent anonymous bind results
- return nil unless ldap_conf.enabled && login.present? && password.present?
-
- ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
- filter = Net::LDAP::Filter.eq(ldap.uid, login)
-
- # Apply LDAP user filter if present
- if ldap_conf['user_filter'].present?
- user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter'])
- filter = Net::LDAP::Filter.join(filter, user_filter)
- end
-
- ldap_user = ldap.bind_as(
- filter: filter,
- size: 1,
- password: password
- )
-
- find_by_uid(ldap_user.dn) if ldap_user
- end
-
- private
-
- def find_by_uid_and_provider
- find_by_uid(uid)
- end
-
- def find_by_uid(uid)
+ def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive
- model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last
+ identity = ::Identity.
+ where(provider: provider).
+ where('lower(extern_uid) = ?', uid.downcase).last
+ identity && identity.user
end
+ end
- def username
- auth.info.nickname.to_s.force_encoding("utf-8")
- end
+ def initialize(auth_hash)
+ super
+ update_user_attributes
+ end
- def provider
- 'ldap'
- end
+ # instance methods
+ def gl_user
+ @gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user
+ end
- def raise_error(message)
- raise OmniAuth::Error, "(LDAP) " + message
- end
+ def find_by_uid_and_provider
+ self.class.find_by_uid_and_provider(
+ auth_hash.uid.downcase, auth_hash.provider)
+ end
- def ldap_conf
- Gitlab.config.ldap
- end
+ def find_by_email
+ ::User.find_by(email: auth_hash.email)
+ end
+
+ def update_user_attributes
+ return unless persisted?
+
+ gl_user.skip_reconfirmation!
+ gl_user.email = auth_hash.email
+
+ # Build new identity only if we dont have have same one
+ gl_user.identities.find_or_initialize_by(provider: auth_hash.provider,
+ extern_uid: auth_hash.uid)
+
+ gl_user
+ end
+
+ def changed?
+ gl_user.changed? || gl_user.identities.any?(&:changed?)
+ end
+
+ def block_after_signup?
+ ldap_config.block_auto_created_users
+ end
+
+ def allowed?
+ Gitlab::LDAP::Access.allowed?(gl_user)
+ end
+
+ def ldap_config
+ Gitlab::LDAP::Config.new(auth_hash.provider)
end
end
end
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index 64cf3303ea..59b21149a9 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -1,5 +1,9 @@
module Gitlab
class Logger < ::Logger
+ def self.file_name
+ file_name_noext + '.log'
+ end
+
def self.error(message)
build.error(message)
end
@@ -15,7 +19,7 @@ module Gitlab
tail_output.split("\n")
end
- def self.read_latest_for filename
+ def self.read_latest_for(filename)
path = Rails.root.join("log", filename)
tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path}))
tail_output.split("\n")
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index b248d8f943..47c456d8dc 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -1,3 +1,6 @@
+require 'html/pipeline'
+require 'html/pipeline/gitlab'
+
module Gitlab
# Custom parser for GitLab-flavored Markdown
#
@@ -11,6 +14,7 @@ module Gitlab
# * !123 for merge requests
# * $123 for snippets
# * 123456 for commits
+ # * 123456...7890123 for commit ranges (comparisons)
#
# It also parses Emoji codes to insert images. See
# http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
@@ -28,14 +32,26 @@ module Gitlab
module Markdown
include IssuesHelper
- attr_reader :html_options
+ attr_reader :options, :html_options
# Public: Parse the provided text with GitLab-Flavored Markdown
#
# text - the source text
- # project - extra options for the reference links as given to link_to
+ # project - the project
# html_options - extra options for the reference links as given to link_to
def gfm(text, project = @project, html_options = {})
+ gfm_with_options(text, {}, project, html_options)
+ end
+
+ # Public: Parse the provided text with GitLab-Flavored Markdown
+ #
+ # text - the source text
+ # options - parse_tasks - render tasks
+ # - xhtml - output XHTML instead of HTML
+ # - reference_only_path - Use relative path for reference links
+ # project - the project
+ # html_options - extra options for the reference links as given to link_to
+ def gfm_with_options(text, options = {}, project = @project, html_options = {})
return text if text.nil?
# Duplicate the string so we don't alter the original, then call to_str
@@ -43,8 +59,61 @@ module Gitlab
# for gsub calls to work as we need them to.
text = text.dup.to_str
+ options.reverse_merge!(
+ parse_tasks: false,
+ xhtml: false,
+ reference_only_path: true
+ )
+
+ @options = options
@html_options = html_options
+ # TODO: add popups with additional information
+
+ # Used markdown pipelines in GitLab:
+ # GitlabEmojiFilter - performs emoji replacement.
+ # SanitizationFilter - remove unsafe HTML tags and attributes
+ #
+ # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
+ filters = [
+ HTML::Pipeline::Gitlab::GitlabEmojiFilter,
+ HTML::Pipeline::SanitizationFilter
+ ]
+
+ whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST
+ whitelist[:attributes][:all].push('class', 'id')
+ whitelist[:elements].push('span')
+
+ # Remove the rel attribute that the sanitize gem adds, and remove the
+ # href attribute if it contains inline javascript
+ fix_anchors = lambda do |env|
+ name, node = env[:node_name], env[:node]
+ if name == 'a'
+ node.remove_attribute('rel')
+ if node['href'] && node['href'].match('javascript:')
+ node.remove_attribute('href')
+ end
+ end
+ end
+ whitelist[:transformers].push(fix_anchors)
+
+ markdown_context = {
+ asset_root: Gitlab.config.gitlab.url,
+ asset_host: Gitlab::Application.config.asset_host,
+ whitelist: whitelist
+ }
+
+ markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
+
+ result = markdown_pipeline.call(text, markdown_context)
+
+ save_options = 0
+ if options[:xhtml]
+ save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
+ end
+
+ text = result[:output].to_html(save_with: save_options)
+
# Extract pre blocks so they are not altered
# from http://github.github.com/github-flavored-markdown/
text.gsub!(%r{
.*?
|.*?}m) { |match| extract_piece(match) }
@@ -53,8 +122,6 @@ module Gitlab
# Extract images with probably parsable src
text.gsub!(%r{}m) { |match| extract_piece(match) }
- # TODO: add popups with additional information
-
text = parse(text, project)
# Insert pre block extractions
@@ -62,12 +129,11 @@ module Gitlab
insert_piece($1)
end
- allowed_attributes = ActionView::Base.sanitized_allowed_attributes
- allowed_tags = ActionView::Base.sanitized_allowed_tags
+ if options[:parse_tasks]
+ text = parse_tasks(text)
+ end
- sanitize text.html_safe,
- attributes: allowed_attributes + %w(id class),
- tags: allowed_tags + %w(table tr td th)
+ text.html_safe
end
private
@@ -84,78 +150,82 @@ module Gitlab
@extractions[id]
end
- # Private: Parses text for references and emoji
+ # Private: Parses text for references
#
# text - Text to parse
#
# Returns parsed text
def parse(text, project = @project)
parse_references(text, project) if project
- parse_emoji(text)
text
end
+ NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR
+ PROJ_STR = "(?#{NAME_STR}/#{NAME_STR})"
+
REFERENCE_PATTERN = %r{
(?\W)? # Prefix
( # Reference
- @(?[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
+ @(?#{NAME_STR}) # User name
+ |~(?