Commit c24f9cb6 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'merge-79190fe0-7.3.0-pre' into 'master'

Merge 79190fe0 (7.3.0-pre) and serialize_service_properties

This combines two merges because each individual merge breaks the build on its own.

See merge request !175
parents 607fe5f0 35eec0f3
...@@ -5,6 +5,8 @@ targets: ...@@ -5,6 +5,8 @@ targets:
debian-7: &wheezy debian-7: &wheezy
build_dependencies: build_dependencies:
- libicu-dev - libicu-dev
- cmake
- pkg-config
dependencies: dependencies:
- libicu48 - libicu48
- libpcre3 - libpcre3
...@@ -13,6 +15,8 @@ targets: ...@@ -13,6 +15,8 @@ targets:
ubuntu-14.04: ubuntu-14.04:
build_dependencies: build_dependencies:
- libicu-dev - libicu-dev
- cmake
- pkg-config
dependencies: dependencies:
- libicu52 - libicu52
- libpcre3 - libpcre3
...@@ -20,6 +24,8 @@ targets: ...@@ -20,6 +24,8 @@ targets:
centos-6: centos-6:
build_dependencies: build_dependencies:
- libicu-devel - libicu-devel
- cmake
- pkgconfig
dependencies: dependencies:
- libicu - libicu
- pcre - pcre
......
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
v 7.3.0
- Always set the 'origin' remote in satellite actions
- Write authorized_keys in tmp/ during tests
- 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)
v 7.2.1
- Delete orphaned labels during label migration (James Brooks)
- Security: prevent XSS with stricter MIME types for raw repo files
v 7.2.0 v 7.2.0
- Explore page - Explore page
- Add project stars (Ciro Santilli) - Add project stars (Ciro Santilli)
......
...@@ -62,6 +62,7 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -62,6 +62,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Create a feature branch 1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code 1. Write [tests](README.md#run-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG) 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. 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. Push the commit to your fork
1. Submit a merge request (MR) to the master branch 1. Submit a merge request (MR) to the master branch
......
...@@ -27,6 +27,7 @@ gem 'omniauth', "~> 1.1.3" ...@@ -27,6 +27,7 @@ gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2' gem 'omniauth-google-oauth2'
gem 'omniauth-twitter' gem 'omniauth-twitter'
gem 'omniauth-github' gem 'omniauth-github'
gem 'omniauth-shibboleth'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
...@@ -36,7 +37,7 @@ gem "gitlab_git", '~> 6.0' ...@@ -36,7 +37,7 @@ gem "gitlab_git", '~> 6.0'
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth # LDAP Auth
gem 'gitlab_omniauth-ldap', '1.0.4', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
gem 'net-ldap' gem 'net-ldap'
# Git Wiki # Git Wiki
...@@ -83,7 +84,7 @@ gem "seed-fu" ...@@ -83,7 +84,7 @@ gem "seed-fu"
gem "github-markup" gem "github-markup"
# Required markup gems by github-markdown # Required markup gems by github-markdown
gem 'redcarpet', '~> 2.2.2' gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth' gem 'RedCloth'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby' gem 'org-ruby'
...@@ -157,6 +158,9 @@ gem "rack-attack" ...@@ -157,6 +158,9 @@ gem "rack-attack"
# Ace editor # Ace editor
gem 'ace-rails-ap' gem 'ace-rails-ap'
# Keyboard shortcuts
gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar # Semantic UI Sass for Sidebar
gem 'semantic-ui-sass', '~> 0.16.1.0' gem 'semantic-ui-sass', '~> 0.16.1.0'
...@@ -232,7 +236,7 @@ group :development, :test do ...@@ -232,7 +236,7 @@ group :development, :test do
gem 'jasmine', '2.0.2' gem 'jasmine', '2.0.2'
gem "spring", '1.1.1' gem "spring", '1.1.3'
gem "spring-commands-rspec", '1.0.1' gem "spring-commands-rspec", '1.0.1'
gem "spring-commands-spinach", '1.0.0' gem "spring-commands-spinach", '1.0.0'
end end
......
...@@ -168,7 +168,7 @@ GEM ...@@ -168,7 +168,7 @@ GEM
multi_json multi_json
gitlab-grack (2.0.0.pre) gitlab-grack (2.0.0.pre)
rack (~> 1.5.1) rack (~> 1.5.1)
gitlab-grit (2.6.10) gitlab-grit (2.6.11)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (~> 1.15) mime-types (~> 1.15)
...@@ -186,8 +186,8 @@ GEM ...@@ -186,8 +186,8 @@ GEM
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
rugged (~> 0.21.0) rugged (~> 0.21.0)
gitlab_meta (7.0) gitlab_meta (7.0)
gitlab_omniauth-ldap (1.0.4) gitlab_omniauth-ldap (1.1.0)
net-ldap (~> 0.3.1) net-ldap (~> 0.7.0)
omniauth (~> 1.0) omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1) pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1) rubyntlm (~> 0.1.1)
...@@ -287,11 +287,12 @@ GEM ...@@ -287,11 +287,12 @@ GEM
mime-types (1.25.1) mime-types (1.25.1)
mini_portile (0.6.0) mini_portile (0.6.0)
minitest (5.3.5) minitest (5.3.5)
mousetrap-rails (1.4.6)
multi_json (1.10.1) multi_json (1.10.1)
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (1.2.0) multipart-post (1.2.0)
mysql2 (0.3.16) mysql2 (0.3.16)
net-ldap (0.3.1) net-ldap (0.7.0)
net-scp (1.1.2) net-scp (1.1.2)
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
net-ssh (2.8.0) net-ssh (2.8.0)
...@@ -320,6 +321,8 @@ GEM ...@@ -320,6 +321,8 @@ GEM
omniauth-oauth2 (1.1.1) omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0) oauth2 (~> 0.8.0)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-shibboleth (1.1.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.0.1) omniauth-twitter (1.0.1)
multi_json (~> 1.3) multi_json (~> 1.3)
omniauth-oauth (~> 1.0) omniauth-oauth (~> 1.0)
...@@ -391,7 +394,7 @@ GEM ...@@ -391,7 +394,7 @@ GEM
ffi (>= 0.5.0) ffi (>= 0.5.0)
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redcarpet (2.2.2) redcarpet (3.1.2)
redis (3.0.6) redis (3.0.6)
redis-actionpack (4.0.0) redis-actionpack (4.0.0)
actionpack (~> 4) actionpack (~> 4)
...@@ -489,7 +492,7 @@ GEM ...@@ -489,7 +492,7 @@ GEM
capybara (>= 2.0.0) capybara (>= 2.0.0)
railties (>= 3) railties (>= 3)
spinach (>= 0.4) spinach (>= 0.4)
spring (1.1.1) spring (1.1.3)
spring-commands-rspec (1.0.1) spring-commands-rspec (1.0.1)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.0.0) spring-commands-spinach (1.0.0)
...@@ -615,7 +618,7 @@ DEPENDENCIES ...@@ -615,7 +618,7 @@ DEPENDENCIES
gitlab_emoji (~> 0.0.1.1) gitlab_emoji (~> 0.0.1.1)
gitlab_git (~> 6.0) gitlab_git (~> 6.0)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.0.4) gitlab_omniauth-ldap (= 1.1.0)
gollum-lib (~> 3.0.0) gollum-lib (~> 3.0.0)
gon (~> 5.0.0) gon (~> 5.0.0)
grape (~> 0.6.1) grape (~> 0.6.1)
...@@ -636,12 +639,14 @@ DEPENDENCIES ...@@ -636,12 +639,14 @@ DEPENDENCIES
launchy launchy
letter_opener letter_opener
minitest (~> 5.3.0) minitest (~> 5.3.0)
mousetrap-rails
mysql2 mysql2
net-ldap net-ldap
nprogress-rails nprogress-rails
omniauth (~> 1.1.3) omniauth (~> 1.1.3)
omniauth-github omniauth-github
omniauth-google-oauth2 omniauth-google-oauth2
omniauth-shibboleth
omniauth-twitter omniauth-twitter
org-ruby org-ruby
pg pg
...@@ -658,7 +663,7 @@ DEPENDENCIES ...@@ -658,7 +663,7 @@ DEPENDENCIES
rb-fsevent rb-fsevent
rb-inotify rb-inotify
rdoc (~> 3.6) rdoc (~> 3.6)
redcarpet (~> 2.2.2) redcarpet (~> 3.1.2)
redis-rails redis-rails
request_store request_store
rspec-rails rspec-rails
...@@ -677,7 +682,7 @@ DEPENDENCIES ...@@ -677,7 +682,7 @@ DEPENDENCIES
slack-notifier (~> 0.3.2) slack-notifier (~> 0.3.2)
slim slim
spinach-rails spinach-rails
spring (= 1.1.1) spring (= 1.1.3)
spring-commands-rspec (= 1.0.1) spring-commands-rspec (= 1.0.1)
spring-commands-spinach (= 1.0.0) spring-commands-spinach (= 1.0.0)
stamp stamp
......
# A sample Guardfile # A sample Guardfile
# More info at https://github.com/guard/guard#readme # 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{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_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" } 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 ...@@ -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" } watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
end end
guard 'spinach' do guard 'spinach', command_prefix: 'spring' do
watch(%r|^features/(.*)\.feature|) watch(%r|^features/(.*)\.feature|)
watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m| watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
"features/#{m[1]}#{m[2]}.feature" "features/#{m[1]}#{m[2]}.feature"
......
...@@ -87,7 +87,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea ...@@ -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 ### 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 ### Improperly formatted merge request
......
# GitLab # ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
## Open source software to collaborate on code ## Open source software to collaborate on code
![logo](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/gitlab_logo.png) ![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif)
![animated-screenshots](https://gist.github.com/fnkr/2f9badd56bfe0ed04ee7/raw/4f48806fbae97f556c2f78d8c2d299c04500cb0d/compiled.gif)
- Manage Git repositories with fine grained access controls that keep your code secure - Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests - Perform code reviews and enhance collaboration with merge requests
...@@ -21,7 +19,9 @@ ...@@ -21,7 +19,9 @@
- [![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) - [![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)
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) - [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
...@@ -29,14 +29,14 @@ ...@@ -29,14 +29,14 @@
## Website ## 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/) - [Subscriptions](https://about.gitlab.com/subscription/)
- [Consultancy](https://www.gitlab.com/consultancy/) - [Consultancy](https://about.gitlab.com/consultancy/)
- [Community](https://www.gitlab.com/community/) - [Community](https://about.gitlab.com/community/)
- [Hosted GitLab.com](https://www.gitlab.com/gitlab-com/) use GitLab as a free service - [Hosted GitLab.com](https://about.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 Enterprise Edition](https://about.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. - [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Third-party applications ## Third-party applications
...@@ -61,15 +61,11 @@ These applications are maintained by contributors, GitLab B.V. does not offer su ...@@ -61,15 +61,11 @@ These applications are maintained by contributors, GitLab B.V. does not offer su
## Installation ## Installation
We recommend the [GitLab Enterprise Edition packages](https://gitlab.com/subscribers/gitlab-ee/blob/master/doc/install/packages.md). Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
You can also use the [Chef cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) or the [manual installation](doc/install/installation.md).
Other options are listed on the [installation page on the GitLab website](https://www.gitlab.com/installation/) but most of these assume GitLab Community Edition.
### New versions ### New versions
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). 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://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).
### Upgrading ### Upgrading
...@@ -89,7 +85,8 @@ Please login with `root` / `5iveL!fe` ...@@ -89,7 +85,8 @@ Please login with `root` / `5iveL!fe`
## Install a development environment ## 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 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 development kit you might need to copy the example development unicorn configuration file
cp config/unicorn.rb.example.development config/unicorn.rb cp config/unicorn.rb.example.development config/unicorn.rb
...@@ -130,7 +127,7 @@ All documentation can be found on [doc.gitlab.com/ee/](http://doc.gitlab.com/ee/ ...@@ -130,7 +127,7 @@ All documentation can be found on [doc.gitlab.com/ee/](http://doc.gitlab.com/ee/
## Getting help ## 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? ## Is it any good?
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#= require jquery.atwho #= require jquery.atwho
#= require jquery.scrollTo #= require jquery.scrollTo
#= require jquery.blockUI #= require jquery.blockUI
#= require jquery.sticky
#= require turbolinks #= require turbolinks
#= require jquery.turbolinks #= require jquery.turbolinks
#= require bootstrap #= require bootstrap
...@@ -25,12 +26,20 @@ ...@@ -25,12 +26,20 @@
#= require branch-graph #= require branch-graph
#= require highlight.pack #= require highlight.pack
#= require ace/ace #= require ace/ace
#= require ace/ext-searchbox
#= require d3 #= require d3
#= require underscore #= require underscore
#= require nprogress #= require nprogress
#= require nprogress-turbolinks #= require nprogress-turbolinks
#= require dropzone #= require dropzone
#= require semantic-ui/sidebar #= 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_tree . #= require_tree .
window.slugify = (text) -> window.slugify = (text) ->
...@@ -117,6 +126,13 @@ $ -> ...@@ -117,6 +126,13 @@ $ ->
# Initialize select2 selects # Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true) $('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 # Initialize tooltips
$('.has_tooltip').tooltip() $('.has_tooltip').tooltip()
...@@ -149,20 +165,6 @@ $ -> ...@@ -149,20 +165,6 @@ $ ->
# Show/Hide the profile menu when hovering the account box # Show/Hide the profile menu when hovering the account box
$('.account-box').hover -> $(@).toggleClass('hover') $('.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 # Commit show suppressed diff
$(".diff-content").on "click", ".supp_diff_link", -> $(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show() $(@).next('table').show()
......
$ ->
# Toggle line wrapping in diff.
#
# %div.diff-file
# %input.js-toggle-diff-line-wrap
# %td.line_content
#
$("body").on "click", ".js-toggle-diff-line-wrap", (e) ->
diffFile = $(@).closest(".diff-file")
if $(@).is(":checked")
diffFile.addClass("diff-wrap-lines")
else
diffFile.removeClass("diff-wrap-lines")
class BranchGraph class @BranchGraph
constructor: (@element, @options) -> constructor: (@element, @options) ->
@preparedCommits = {} @preparedCommits = {}
@mtime = 0 @mtime = 0
...@@ -120,23 +120,32 @@ class BranchGraph ...@@ -120,23 +120,32 @@ class BranchGraph
@top.toFront() @top.toFront()
bindEvents: -> bindEvents: ->
drag = {}
element = @element element = @element
$(element).scroll (event) => $(element).scroll (event) =>
@renderPartialGraph() @renderPartialGraph()
$(window).on scrollDown: =>
keydown: (event) => @element.scrollTop @element.scrollTop() + 50
# left @renderPartialGraph()
element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37
# top scrollUp: =>
element.scrollTop element.scrollTop() - 50 if event.keyCode is 38 @element.scrollTop @element.scrollTop() - 50
# right @renderPartialGraph()
element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39
# bottom scrollLeft: =>
element.scrollTop element.scrollTop() + 50 if event.keyCode is 40 @element.scrollLeft @element.scrollLeft() - 50
@renderPartialGraph() @renderPartialGraph()
scrollRight: =>
@element.scrollLeft @element.scrollLeft() + 50
@renderPartialGraph()
scrollBottom: =>
@element.scrollTop @element.find('svg').height()
scrollTop: =>
@element.scrollTop 0
appendLabel: (x, y, commit) -> appendLabel: (x, y, commit) ->
return unless commit.refs return unless commit.refs
...@@ -325,5 +334,3 @@ Raphael::textWrap = (t, width) -> ...@@ -325,5 +334,3 @@ Raphael::textWrap = (t, width) ->
b = t.getBBox() b = t.getBBox()
h = Math.abs(b.y2) - Math.abs(b.y) + 1 h = Math.abs(b.y2) - Math.abs(b.y) + 1
t.attr y: b.y + h t.attr y: b.y + h
@BranchGraph = BranchGraph
...@@ -34,7 +34,8 @@ class Diff ...@@ -34,7 +34,8 @@ class Diff
$.get(link, params, (response) => $.get(link, params, (response) =>
target.parent().replaceWith(response) target.parent().replaceWith(response)
) )
) ).ready =>
$(".diff-header").sticky {responsiveWidth:true, getWidthFrom: ".diff-file"}
lineNumbers: (line) -> lineNumbers: (line) ->
return ([0, 0]) unless line.children().length return ([0, 0]) unless line.children().length
......
...@@ -15,49 +15,87 @@ class Dispatcher ...@@ -15,49 +15,87 @@ class Dispatcher
return false return false
path = page.split(':') path = page.split(':')
shortcut_handler = null
switch page switch page
when 'projects:issues:index' when 'projects:issues:index'
Issues.init() Issues.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show' when 'projects:issues:show'
new Issue() new Issue()
shortcut_handler = new ShortcutsIssueable()
new ZenMode()
when 'projects:milestones:show' when 'projects:milestones:show'
new Milestone() new Milestone()
when 'projects:issues:new' when 'projects:issues:new','projects:issues:edit'
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
when 'projects:merge_requests:new' shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'projects:merge_requests:new', 'projects:merge_requests:edit'
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
new Diff() new Diff()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'projects:merge_requests:show' when 'projects:merge_requests:show'
new Diff() new Diff()
shortcut_handler = new ShortcutsIssueable()
new ZenMode()
when "projects:merge_requests:diffs" when "projects:merge_requests:diffs"
new Diff() new Diff()
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
when 'dashboard:show' when 'dashboard:show'
new Dashboard() new Dashboard()
new Activities() new Activities()
when 'projects:commit:show' when 'projects:commit:show'
new Commit() new Commit()
new Diff() new Diff()
shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation()
when 'groups:show', 'projects:show' when 'groups:show', 'projects:show'
new Activities() new Activities()
when 'projects:new', 'projects:edit' shortcut_handler = new ShortcutsNavigation()
when 'projects:new'
new Project() new Project()
when 'projects:edit'
new Project()
shortcut_handler = new ShortcutsNavigation()
when 'projects:teams:members:index' when 'projects:teams:members:index'
new TeamMembers() new TeamMembers()
when 'groups:members' when 'groups:members'
new GroupMembers() new GroupMembers()
when 'projects:tree:show' when 'projects:tree:show'
new TreeView() new TreeView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:blob:show' when 'projects:blob:show'
new BlobView() new BlobView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit' when 'projects:labels:new', 'projects:labels:edit'
new Labels() 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
switch path.first() switch path.first()
when 'admin' then new Admin() when 'admin' then new Admin()
when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation()
when 'projects' when 'projects'
new Wikis() if path[1] == 'wikis' switch path[1]
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
when 'snippets', 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'team_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: -> initSearch: ->
opts = $('.search-autocomplete-opts') opts = $('.search-autocomplete-opts')
......
...@@ -43,25 +43,31 @@ ...@@ -43,25 +43,31 @@
$(".selected_issue").bind "change", Issues.checkChanged $(".selected_issue").bind "change", Issues.checkChanged
# Make sure we trigger ajax request only after user stop typing
initSearch: -> initSearch: ->
form = $("#issue_search_form") @timer = null
last_terms = ""
$("#issue_search").keyup -> $("#issue_search").keyup ->
terms = $(this).val() clearTimeout(@timer);
unless terms is last_terms @timer = setTimeout(Issues.filterResults, 500)
last_terms = terms
if terms.length >= 2 or terms.length is 0 filterResults: =>
$.ajax form = $("#issue_search_form")
type: "GET" search = $("#issue_search").val()
url: location.href $('.issues-holder').css("opacity", '0.5')
data: "issue_search=" + terms issues_url = form.attr('action') + '? '+ form.serialize()
complete: ->
$(".loading").hide() $.ajax
success: (data) -> type: "GET"
$('.issues-holder').html(data.html) url: form.attr('action')
Issues.reload() data: form.serialize()
dataType: "json" complete: ->
$('.issues-holder').css("opacity", '1.0')
success: (data) ->
$('.issues-holder').html(data.html)
# Change url so if user reload a page - search results are saved
History.replaceState {page: issues_url}, document.title, issues_url
Issues.reload()
dataType: "json"
checkChanged: -> checkChanged: ->
checked_issues = $(".selected_issue:checked") checked_issues = $(".selected_issue:checked")
......
...@@ -27,7 +27,7 @@ $(document).ready -> ...@@ -27,7 +27,7 @@ $(document).ready ->
dropzone = $(".div-dropzone").dropzone( dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload url: project_image_path_upload
dictDefaultMessage: "" dictDefaultMessage: ""
clickable: true clickable: false
paramName: "markdown_img" paramName: "markdown_img"
maxFilesize: 10 maxFilesize: 10
uploadMultiple: false uploadMultiple: false
......
class Network class @Network
constructor: (opts) -> constructor: (opts) ->
$("#filter_ref").click -> $("#filter_ref").click ->
$(this).closest('form').submit() $(this).closest('form').submit()
branch_graph = new BranchGraph($(".network-graph"), opts) @branch_graph = new BranchGraph($(".network-graph"), opts)
vph = $(window).height() - 250 vph = $(window).height() - 250
$('.network-graph').css 'height': (vph + 'px') $('.network-graph').css 'height': (vph + 'px')
@Network = Network
...@@ -16,13 +16,19 @@ class Notes ...@@ -16,13 +16,19 @@ class Notes
$(document).on "ajax:success", ".js-main-target-form", @addNote $(document).on "ajax:success", ".js-main-target-form", @addNote
$(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote
# change note in UI after update # change note in UI after update
$(document).on "ajax:success", "form.edit_note", @updateNote $(document).on "ajax:success", "form.edit_note", @updateNote
# Edit note link # Edit note link
$(document).on "click", ".js-note-edit", @showEditForm $(document).on "click", ".js-note-edit", @showEditForm
$(document).on "click", ".note-edit-cancel", @cancelEdit $(document).on "click", ".note-edit-cancel", @cancelEdit
# Reopen and close actions for Issue/MR combined with note form submit
$(document).on "click", ".js-note-target-reopen", @targetReopen
$(document).on "click", ".js-note-target-close", @targetClose
$(document).on "click", ".js-comment-button", @updateCloseButton
$(document).on "keyup", ".js-note-text", @updateTargetButtons
# remove a note (in general) # remove a note (in general)
$(document).on "click", ".js-note-delete", @removeNote $(document).on "click", ".js-note-delete", @removeNote
...@@ -78,7 +84,9 @@ class Notes ...@@ -78,7 +84,9 @@ class Notes
$(document).off "click", ".js-add-diff-note-button" $(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange" $(document).off "visibilitychange"
$(document).off "keypress", @notes_forms $(document).off "keypress", @notes_forms
$(document).off "keyup", ".js-note-text"
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
initRefresh: -> initRefresh: ->
clearInterval(Notes.interval) clearInterval(Notes.interval)
...@@ -406,30 +414,6 @@ class Notes ...@@ -406,30 +414,6 @@ class Notes
form.find(".js-note-text").focus() form.find(".js-note-text").focus()
form.addClass "js-discussion-note-form" form.addClass "js-discussion-note-form"
###
General note form setup.
deactivates the submit button when text is empty
hides the preview button when text is empty
setup GFM auto complete
show the form
###
setupNoteForm: (form) =>
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
form.removeClass "js-new-note-form"
form.removeClass "js-new-note-form"
GitLab.GfmAutoComplete.setup()
# setup preview buttons
previewButton = form.find(".js-note-preview-button")
form.find(".js-note-text").on "input", ->
if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on"
else
previewButton.removeClass("turn-on").addClass "turn-off"
form.show()
### ###
Called when clicking on the "add a comment" button on the side of a diff line. Called when clicking on the "add a comment" button on the side of a diff line.
...@@ -502,4 +486,33 @@ class Notes ...@@ -502,4 +486,33 @@ class Notes
visibilityChange: => visibilityChange: =>
@refresh() @refresh()
targetReopen: (e) =>
@submitNoteForm($(e.target).parents('form'))
targetClose: (e) =>
@submitNoteForm($(e.target).parents('form'))
submitNoteForm: (form) =>
noteText = form.find(".js-note-text").val()
if noteText.trim().length > 0
form.submit()
updateCloseButton: (e) =>
textarea = $(e.target)
form = textarea.parents('form')
form.find('.js-note-target-close').text('Close')
updateTargetButtons: (e) =>
textarea = $(e.target)
form = textarea.parents('form')
if textarea.val().trim().length > 0
form.find('.js-note-target-reopen').text('Comment & reopen')
form.find('.js-note-target-close').text('Comment & close')
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
@Notes = Notes @Notes = Notes
...@@ -55,7 +55,7 @@ $ -> ...@@ -55,7 +55,7 @@ $ ->
$(@).parents('.no-ssh-key-message').hide() $(@).parents('.no-ssh-key-message').hide()
e.preventDefault() e.preventDefault()
$('.project-side .star').on 'ajax:success', (e, data, status, xhr) -> $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count) $(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) -> .on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert') new Flash('Star toggle failed. Try again later.', 'alert')
class Shortcuts class @Shortcuts
constructor: -> constructor: ->
@enabledHelp = []
Mousetrap.reset()
Mousetrap.bind('?', @selectiveHelp)
Mousetrap.bind('s', Shortcuts.focusSearch)
selectiveHelp: (e) =>
Shortcuts.showHelp(e, @enabledHelp)
@showHelp: (e, location) ->
if $('#modal-shortcuts').length > 0 if $('#modal-shortcuts').length > 0
$('#modal-shortcuts').modal('show') $('#modal-shortcuts').modal('show')
else else
$.ajax( $.ajax(
url: '/help/shortcuts', url: '/help/shortcuts',
dataType: "script" dataType: 'script',
success: (e) ->
if location and location.length > 0
for l in location
$(l).show()
else
$('.hidden-shortcut').show()
$('.js-more-help-button').remove()
) )
e.preventDefault()
@Shortcuts = Shortcuts @focusSearch: (e) ->
$('#search').focus()
e.preventDefault()
#= require shortcuts
class @ShortcutsDashboardNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-activity'))
Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-projects'))
Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-merge_requests'))
@findAndollowLink: (selector) ->
link = $(selector).attr('href')
if link
window.location = link
#= require shortcuts_navigation
class @ShortcutsIssueable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
Mousetrap.bind('a', ->
$('.js-assignee').select2('open')
return false
)
Mousetrap.bind('m', ->
$('.js-milestone').select2('open')
return false
)
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_reuests')
else
@enabledHelp.push('.hidden-shortcut.issues')
#= require shortcuts
class @ShortcutsNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndollowLink('.shortcuts-project'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndollowLink('.shortcuts-snippets'))
@enabledHelp.push('.hidden-shortcut.project')
@findAndollowLink: (selector) ->
link = $(selector).attr('href')
if link
window.location = link
#= require shortcuts_navigation
class @ShortcutsNetwork extends ShortcutsNavigation
constructor: (@graph) ->
super()
Mousetrap.bind(['left', 'h'], @graph.scrollLeft)
Mousetrap.bind(['right', 'l'], @graph.scrollRight)
Mousetrap.bind(['up', 'k'], @graph.scrollUp)
Mousetrap.bind(['down', 'j'], @graph.scrollDown)
Mousetrap.bind(['shift+up', 'shift+k'], @graph.scrollTop)
Mousetrap.bind(['shift+down', 'shift+j'], @graph.scrollBottom)
@enabledHelp.push('.hidden-shortcut.network')
class @ZenMode
@fullscreen_prefix = 'fullscreen_'
@ESC = 27
constructor: ->
@active_zen_area = null
@active_checkbox = null
$('body').on 'change', '.zennable input[type=checkbox]', (e) =>
checkbox = e.currentTarget;
if checkbox.checked
Mousetrap.pause()
@udpateActiveZenArea(checkbox)
else
@exitZenMode()
$(document).on 'keydown', (e) =>
console.log("esc")
if e.keyCode is ZenMode.ESC
@exitZenMode()
$(window).on 'hashchange', @updateZenModeFromLocationHash
udpateActiveZenArea: (checkbox) =>
@active_checkbox = $(checkbox)
@active_checkbox.prop('checked', true)
@active_zen_area = @active_checkbox.parent().find('textarea')
@active_zen_area.focus()
window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id')
exitZenMode: =>
if @active_zen_area isnt null
Mousetrap.unpause()
@active_checkbox.prop('checked', false)
@active_zen_area = null
@active_checkbox = null
window.location.hash = ''
checkboxFromLocationHash: (e) ->
id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, ''))
if id
return $('.zennable input[type=checkbox]#' + id)[0]
else
return null
updateZenModeFromLocationHash: (e) =>
checkbox = @checkboxFromLocationHash()
if checkbox
@udpateActiveZenArea(checkbox)
else
@exitZenMode()
...@@ -128,7 +128,7 @@ p.time { ...@@ -128,7 +128,7 @@ p.time {
} }
.highlight_word { .highlight_word {
border-bottom: 2px solid #F90; background: #fafe3d;
} }
.thin_area{ .thin_area{
......
...@@ -83,3 +83,140 @@ label { ...@@ -83,3 +83,140 @@ label {
.form-control { .form-control {
@include box-shadow(none); @include box-shadow(none);
} }
.issuable-description {
margin-top: 35px;
}
.zennable {
position: relative;
input {
display: none;
}
.collapse {
display: none;
opacity: 0.5;
&:before {
content: '\f066';
font-family: FontAwesome;
color: #000;
font-size: 28px;
position: relative;
padding: 30px 40px 0 0;
}
&:hover {
opacity: 0.8;
}
}
.expand {
opacity: 0.5;
&:before {
content: '\f065';
font-family: FontAwesome;
color: #000;
font-size: 14px;
line-height: 14px;
padding-right: 20px;
position: relative;
vertical-align: middle;
}
&:hover {
opacity: 0.8;
}
}
input:checked ~ .zen-backdrop .expand {
display: none;
}
input:checked ~ .zen-backdrop .collapse {
display: block;
position: absolute;
top: 0;
}
label {
position: absolute;
top: -26px;
right: 0;
font-variant: small-caps;
text-transform: uppercase;
font-size: 10px;
padding: 4px;
font-weight: 500;
letter-spacing: 1px;
&:before {
display: inline-block;
width: 10px;
height: 14px;
}
}
input:checked ~ .zen-backdrop {
background-color: white;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1031;
textarea {
border: none;
box-shadow: none;
border-radius: 0;
color: #000;
font-size: 20px;
line-height: 26px;
padding: 30px;
display: block;
outline: none;
resize: none;
height: 100vh;
max-width: 900px;
margin: 0 auto;
}
}
.zen-backdrop textarea::-webkit-input-placeholder {
color: white;
}
.zen-backdrop textarea:-moz-placeholder {
color: white;
}
.zen-backdrop textarea::-moz-placeholder {
color: white;
}
.zen-backdrop textarea:-ms-input-placeholder {
color: white;
}
input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder {
color: #999;
}
input:checked ~ .zen-backdrop textarea:-moz-placeholder {
color: #999;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea::-moz-placeholder {
color: #999;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea:-ms-input-placeholder {
color: #999;
}
}
...@@ -88,6 +88,10 @@ ...@@ -88,6 +88,10 @@
.description { .description {
padding: 0 15px 10px 15px; padding: 0 15px 10px 15px;
code {
white-space: pre-wrap;
}
} }
.title, .context, .description { .title, .context, .description {
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
&:hover { &:hover {
background: $hover; background: $hover;
border-bottom: 1px solid #ADF; border-bottom: 1px solid darken($hover, 10%);
} }
&:last-child { &:last-child {
......
.timeline {
list-style: none;
padding: 20px 0 20px;
position: relative;
&:before {
top: 0;
bottom: 0;
position: absolute;
content: " ";
width: 3px;
background-color: #eeeeee;
margin-left: 29px;
}
.timeline-entry {
position: relative;
margin-top: 5px;
margin-left: 30px;
margin-bottom: 10px;
clear: both;
&:target {
.timeline-entry-inner .timeline-content {
-webkit-animation:target-note 2s linear;
background: $hover;
}
}
.timeline-entry-inner {
position: relative;
margin-left: -20px;
&:before, &:after {
content: " ";
display: table;
}
.timeline-icon {
margin-top: 2px;
background: #fff;
color: #737881;
float: left;
@include border-radius(40px);
@include box-shadow(0 0 0 3px #EEE);
overflow: hidden;
.avatar {
margin: 0;
padding: 0;
}
}
.timeline-content {
position: relative;
background: #f5f5f6;
padding: 10px 15px;
margin-left: 60px;
&:after {
content: '';
display: block;
position: absolute;
width: 0;
height: 0;
border-style: solid;
border-width: 9px 9px 9px 0;
border-color: transparent #f5f5f6 transparent transparent;
left: 0;
top: 10px;
margin-left: -9px;
}
}
}
}
}
...@@ -40,7 +40,7 @@ a { ...@@ -40,7 +40,7 @@ a {
outline: none; outline: none;
color: $link_color; color: $link_color;
&:hover { &:hover {
text-decoration: none; text-decoration: underline;
color: $link_hover_color; color: $link_hover_color;
} }
...@@ -89,6 +89,8 @@ a:focus { ...@@ -89,6 +89,8 @@ a:focus {
.wiki { .wiki {
@include md-typography; @include md-typography;
word-wrap: break-word;
/* Link to current header. */ /* Link to current header. */
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
position: relative; position: relative;
......
...@@ -16,3 +16,4 @@ body { ...@@ -16,3 +16,4 @@ body {
.container .content { .container .content {
margin: 0 0; margin: 0 0;
} }
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
* General Colors * General Colors
*/ */
$style_color: #474D57; $style_color: #474D57;
$hover: #D9EDF7; $hover: #FFECDB;
/* /*
* Link colors * Link colors
*/ */
$link_color: #446e9b; $link_color: #446e9b;
$link_hover_color: #2FA0BB; $link_hover_color: darken($link-color, 10%);
$btn-border: 1px solid #ccc; $btn-border: 1px solid #ccc;
......
...@@ -60,12 +60,13 @@ ...@@ -60,12 +60,13 @@
} }
.project-row, .group-row { .project-row, .group-row {
padding: 8px 15px !important; padding: 0 !important;
font-size: 14px; font-size: 14px;
line-height: 24px; line-height: 24px;
a { a {
display: block; display: block;
padding: 8px 15px;
} }
.project-name, .group-name { .project-name, .group-name {
...@@ -99,14 +100,9 @@ ...@@ -99,14 +100,9 @@
margin-right: 15px; margin-right: 15px;
font-size: 20px; font-size: 20px;
margin-bottom: 15px; margin-bottom: 15px;
border: 1px solid #EEE;
padding: 8px 12px;
border-radius: 50px;
background: #f5f5f5;
text-align: center;
i { i {
color: #BBB; color: #888;
} }
} }
......
...@@ -125,8 +125,6 @@ ...@@ -125,8 +125,6 @@
} }
.line_content { .line_content {
display: block; display: block;
white-space: pre;
height: 18px;
margin: 0px; margin: 0px;
padding: 0px 0.5em; padding: 0px 0.5em;
border: none; border: none;
...@@ -341,3 +339,12 @@ ...@@ -341,3 +339,12 @@
margin: 0; margin: 0;
border: none; border: none;
} }
.diff-file .line_content {
white-space: pre;
}
.diff-wrap-lines .line_content {
white-space: pre-wrap;
}
...@@ -17,3 +17,56 @@ ...@@ -17,3 +17,56 @@
} }
} }
} }
.shortcut-mappings {
font-size: 12px;
color: #555;
tbody:first-child tr:first-child {
padding-top: 0
}
th {
padding-top: 15px;
font-size: 14px;
line-height: 1.5;
color: #333;
text-align: left
}
td {
padding-top: 3px;
padding-bottom: 3px;
vertical-align: top;
line-height: 20px
}
.shortcut {
padding-right: 10px;
color: #999;
text-align: right;
white-space: nowrap
}
.key {
@extend .label;
@extend .label-inverse;
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
padding: 3px 5px;
}
}
.modal-body {
position: relative;
overflow-y: auto;
padding: 15px;
}
body.modal-open {
overflow: hidden;
}
.modal .modal-dialog {
width: 860px;
}
...@@ -6,6 +6,21 @@ ...@@ -6,6 +6,21 @@
} }
.login-box{ .login-box{
padding: 0 15px;
.login-heading h3 {
font-weight: 300;
line-height: 2;
}
.login-footer {
margin-top: 10px;
}
.btn {
padding: 12px !important;
@extend .btn-block;
}
} }
.brand-image { .brand-image {
...@@ -19,7 +34,7 @@ ...@@ -19,7 +34,7 @@
} }
} }
.login-logo{ .login-logo {
margin: 10px 0 30px 0; margin: 10px 0 30px 0;
display: block; display: block;
} }
...@@ -64,4 +79,8 @@ ...@@ -64,4 +79,8 @@
color: #a00; color: #a00;
} }
} }
.brand-holder {
border-right: 1px solid #EEE;
}
} }
...@@ -35,39 +35,31 @@ ...@@ -35,39 +35,31 @@
width: 1%; width: 1%;
&.active { &.active {
a { a {
color: #333; color: $link_color;
font-weight: bold; font-weight: bold;
&:after { &:after {
content: ''; content: '';
display: block; display: block;
position: relative; position: relative;
bottom: 8px; bottom: -1px;
left: 50%; border-color: $link_color;
width: 0;
height: 0;
border-color: transparent transparent #333 transparent;
border-style: solid; border-style: solid;
border-width: 6px; border-width: 2px;
margin-left: -6px;
} }
} }
} }
&:hover { &:hover {
a { a {
color: $link_color; color: $link_hover_color;
&:after { &:after {
content: ''; content: '';
display: block; display: block;
position: relative; position: relative;
bottom: 8px; bottom: -1px;
left: 50%; border-color: $link_hover_color;
width: 0;
height: 0;
border-color: transparent transparent $link_color transparent;
border-style: solid; border-style: solid;
border-width: 6px; border-width: 2px;
margin-left: -6px;
} }
} }
} }
...@@ -90,7 +82,6 @@ ...@@ -90,7 +82,6 @@
line-height: 34px; line-height: 34px;
color: #777; color: #777;
text-shadow: 0 1px 1px white; text-shadow: 0 1px 1px white;
padding: 0 10px;
text-decoration: none; text-decoration: none;
padding-top: 2px; padding-top: 2px;
} }
......
...@@ -17,7 +17,6 @@ ul.notes { ...@@ -17,7 +17,6 @@ ul.notes {
.discussion-header, .discussion-header,
.note-header { .note-header {
@extend .cgray; @extend .cgray;
padding-top: 5px;
padding-bottom: 15px; padding-bottom: 15px;
.avatar { .avatar {
...@@ -43,34 +42,19 @@ ul.notes { ...@@ -43,34 +42,19 @@ ul.notes {
} }
.discussion { .discussion {
padding: 10px 0;
overflow: hidden; overflow: hidden;
display: block; display: block;
position:relative; position:relative;
border-bottom: 1px solid #EEE;
.discussion-body {
margin-left: 50px;
}
} }
.note { .note {
padding: 8px 0;
overflow: hidden;
display: block; display: block;
position:relative; position:relative;
border-bottom: 1px solid #eee;
p { color: $style_color; }
.avatar {
margin-top: 3px;
}
.attachment { .attachment {
font-size: 14px; font-size: 14px;
} }
.note-body { .note-body {
@include md-typography; @include md-typography;
margin-left: 43px;
} }
.note-header { .note-header {
padding-bottom: 3px; padding-bottom: 3px;
...@@ -80,11 +64,6 @@ ul.notes { ...@@ -80,11 +64,6 @@ ul.notes {
border-bottom: none; border-bottom: none;
} }
} }
.note:target {
-webkit-animation:target-note 2s linear;
background: #fffff0;
}
} }
.diff-file .notes_holder { .diff-file .notes_holder {
...@@ -99,7 +78,7 @@ ul.notes { ...@@ -99,7 +78,7 @@ ul.notes {
&.notes_line { &.notes_line {
text-align: center; text-align: center;
padding: 10px 0; padding: 10px 0;
background: #eee; background: #FFF;
} }
&.notes_line2 { &.notes_line2 {
text-align: center; text-align: center;
...@@ -111,6 +90,9 @@ ul.notes { ...@@ -111,6 +90,9 @@ ul.notes {
border-width: 1px 0; border-width: 1px 0;
padding-top: 0; padding-top: 0;
vertical-align: top; vertical-align: top;
&.parallel{
border-width: 1px;
}
} }
} }
} }
...@@ -362,3 +344,7 @@ ul.notes { ...@@ -362,3 +344,7 @@ ul.notes {
border-top: 1px solid #DDD; border-top: 1px solid #DDD;
} }
} }
.discussion-notes-count {
font-size: 16px;
}
...@@ -15,62 +15,64 @@ ...@@ -15,62 +15,64 @@
} }
.project-home-panel { .project-home-panel {
border-bottom: 1px solid #DDD; margin-bottom: 15px;
padding-bottom: 15px;
margin-bottom: 30px;
&.empty-project { &.empty-project {
border-bottom: 0px; border-bottom: 0px;
padding-bottom: 15px; padding-bottom: 15px;
margin-bottom: 0px; margin-bottom: 0px;
} }
.project-home-title {
font-size: 18px;
color: #444;
margin: 0;
line-height: 32px;
}
.project-home-dropdown { .project-home-dropdown {
margin-left: 10px; margin-left: 10px;
float: right; float: right;
} }
.project-home-extra {
margin-top: 15px; .project-home-row {
@extend .clearfix;
margin-bottom: 15px;
.project-home-desc { .project-home-desc {
float: left; float: left;
color: #777; color: #666;
margin-bottom: 10px; font-size: 16px;
} }
.project-home-links { .star-fork-buttons {
float: right; float: right;
a { width: 200px;
margin-left: 10px; font-size: 14px;
font-weight: 500; font-weight: bold;
.star-buttons, .fork-buttons {
float: right;
margin-left: 20px;
.count {
margin-left: 5px;
}
} }
} }
} }
.visibility-level-label { .visibility-level-label {
font-size: 17px; color: #555;
background: #f1f1f1; font-weight: bold;
border-radius: 4px;
color: #444;
position: absolute;
margin-left: -55px;
text-shadow: 0 1px 1px #FFF;
width: 40px;
text-align: center;
padding: 6px;
i { i {
color: inherit; color: inherit;
} }
} }
} }
.project-home-links {
padding: 10px 0px;
float: right;
a {
margin-left: 10px;
font-weight: 500;
}
}
.git-clone-holder { .git-clone-holder {
.project-home-dropdown + & { .project-home-dropdown + & {
margin-right: 45px; margin-right: 45px;
...@@ -159,6 +161,7 @@ ul.nav.nav-projects-tabs { ...@@ -159,6 +161,7 @@ ul.nav.nav-projects-tabs {
li { li {
.project-info { .project-info {
margin-bottom: 10px; margin-bottom: 10px;
overflow: hidden;
} }
.access-icon { .access-icon {
...@@ -195,8 +198,8 @@ ul.nav.nav-projects-tabs { ...@@ -195,8 +198,8 @@ ul.nav.nav-projects-tabs {
white-space: normal; white-space: normal;
text-align: left; text-align: left;
padding: 10px 15px; padding: 10px 15px;
background-color: #F1f1f1; background-color: #F9F9F9;
border-color: #EEE; border-color: #DDD;
&:hover { &:hover {
background-color: #eee; background-color: #eee;
......
.search-results {
.search-result-row {
border-bottom: 1px solid #EEE;
padding-bottom: 10px;
margin-bottom: 10px;
}
}
...@@ -17,9 +17,17 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -17,9 +17,17 @@ class Projects::BranchesController < Projects::ApplicationController
end end
def create def create
@branch = CreateBranchService.new.execute(project, params[:branch_name], params[:ref], current_user) result = CreateBranchService.new.execute(project,
params[:branch_name],
redirect_to project_tree_path(@project, @branch.name) params[:ref],
current_user)
if result[:status] == :success
@branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name)
else
@error = result[:message]
render action: 'new'
end
end end
def destroy def destroy
......
...@@ -31,7 +31,7 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -31,7 +31,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
include_diff_info: true) include_diff_info: true)
@diff = Gitlab::DiffParser.new(diffy.diff.scan(/.*\n/)) @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
render layout: false render layout: false
end end
......
...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
terms = params['issue_search'] terms = params['issue_search']
@issues = issues_filtered @issues = issues_filtered
@issues = @issues.where("title LIKE ? OR description LIKE ?", "%#{terms}%", "%#{terms}%") if terms.present? @issues = @issues.full_search(terms) if terms.present?
@issues = @issues.page(params[:page]).per(20) @issues = @issues.page(params[:page]).per(20)
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
......
...@@ -52,7 +52,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -52,7 +52,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' } format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' }
format.js { render nothing: true } format.js
end end
end end
......
...@@ -30,8 +30,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -30,8 +30,10 @@ class Projects::NotesController < Projects::ApplicationController
end end
def update def update
note.update_attributes(note_params) if note.editable?
note.reset_events_cache note.update_attributes(note_params)
note.reset_events_cache
end
respond_to do |format| respond_to do |format|
format.json { render_note_json(note) } format.json { render_note_json(note) }
...@@ -40,8 +42,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -40,8 +42,10 @@ class Projects::NotesController < Projects::ApplicationController
end end
def destroy def destroy
note.destroy if note.editable?
note.reset_events_cache note.destroy
note.reset_events_cache
end
respond_to do |format| respond_to do |format|
format.js { render nothing: true } format.js { render nothing: true }
......
...@@ -29,12 +29,10 @@ class Projects::RawController < Projects::ApplicationController ...@@ -29,12 +29,10 @@ class Projects::RawController < Projects::ApplicationController
private private
def get_blob_type def get_blob_type
if @blob.mime_type =~ /html|javascript/ if @blob.text?
'text/plain; charset=utf-8' 'text/plain; charset=utf-8'
elsif @blob.name =~ /(?:msi|exe|rar|r0\d|7z|7zip|zip)$/
'application/octet-stream'
else else
@blob.mime_type 'application/octet-stream'
end end
end end
end end
......
...@@ -63,7 +63,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -63,7 +63,7 @@ class Projects::SnippetsController < Projects::ApplicationController
def raw def raw
send_data( send_data(
@snippet.content, @snippet.content,
type: "text/plain", type: 'text/plain; charset=utf-8',
disposition: 'inline', disposition: 'inline',
filename: @snippet.file_name filename: @snippet.file_name
) )
......
...@@ -13,10 +13,16 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -13,10 +13,16 @@ class Projects::TagsController < Projects::ApplicationController
end end
def create def create
@tag = CreateTagService.new.execute(@project, params[:tag_name], result = CreateTagService.new.execute(@project, params[:tag_name],
params[:ref], current_user) params[:ref], params[:message],
current_user)
redirect_to project_tags_path(@project) if result[:status] == :success
@tag = result[:tag]
redirect_to project_tags_path(@project)
else
@error = result[:message]
render action: 'new'
end
end end
def destroy def destroy
...@@ -28,7 +34,7 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -28,7 +34,7 @@ class Projects::TagsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to project_tags_path } format.html { redirect_to project_tags_path }
format.js { render nothing: true } format.js
end end
end end
end end
...@@ -103,7 +103,15 @@ class ProjectsController < ApplicationController ...@@ -103,7 +103,15 @@ class ProjectsController < ApplicationController
::Projects::DestroyService.new(@project, current_user, {}).execute ::Projects::DestroyService.new(@project, current_user, {}).execute
respond_to do |format| respond_to do |format|
format.html { redirect_to root_path } format.html do
flash[:alert] = "Project deleted."
if request.referer.include?("/admin")
redirect_to admin_projects_path
else
redirect_to projects_dashboard_path
end
end
end end
end end
......
...@@ -4,14 +4,33 @@ class SearchController < ApplicationController ...@@ -4,14 +4,33 @@ class SearchController < ApplicationController
def show def show
@project = Project.find_by(id: params[:project_id]) if params[:project_id].present? @project = Project.find_by(id: params[:project_id]) if params[:project_id].present?
@group = Group.find_by(id: params[:group_id]) if params[:group_id].present? @group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
if @project @search_results = if @project
return access_denied! unless can?(current_user, :download_code, @project) return access_denied! unless can?(current_user, :download_code, @project)
@search_results = Search::ProjectService.new(@project, current_user, params).execute unless %w(blobs notes issues merge_requests wiki_blobs).
else include?(@scope)
@search_results = Search::GlobalService.new(current_user, params).execute @scope = 'blobs'
end end
Search::ProjectService.new(@project, current_user, params).execute
elsif @show_snippets
unless %w(snippet_blobs snippet_titles).include?(@scope)
@scope = 'snippet_blobs'
end
Search::SnippetService.new(current_user, params).execute
else
unless %w(projects issues merge_requests).include?(@scope)
@scope = 'projects'
end
Search::GlobalService.new(current_user, params).execute
end
@objects = @search_results.objects(@scope, params[:page])
end end
def autocomplete def autocomplete
......
...@@ -86,7 +86,7 @@ class SnippetsController < ApplicationController ...@@ -86,7 +86,7 @@ class SnippetsController < ApplicationController
def raw def raw
send_data( send_data(
@snippet.content, @snippet.content,
type: "text/plain", type: 'text/plain; charset=utf-8',
disposition: 'inline', disposition: 'inline',
filename: @snippet.file_name filename: @snippet.file_name
) )
......
# BaseFinder # IssuableFinder
# #
# Used to filter Issues and MergeRequests collections by set of params # Used to filter Issues and MergeRequests collections by set of params
# #
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
# label_name: string # label_name: string
# sort: string # sort: string
# #
class BaseFinder require_relative 'projects_finder'
class IssuableFinder
attr_accessor :current_user, :params attr_accessor :current_user, :params
def execute(current_user, params) def execute(current_user, params)
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
# label_name: string # label_name: string
# sort: string # sort: string
# #
class IssuesFinder < BaseFinder class IssuesFinder < IssuableFinder
def klass def klass
Issue Issue
end end
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
# label_name: string # label_name: string
# sort: string # sort: string
# #
class MergeRequestsFinder < BaseFinder class MergeRequestsFinder < IssuableFinder
def klass def klass
MergeRequest MergeRequest
end end
......
...@@ -178,6 +178,8 @@ module ApplicationHelper ...@@ -178,6 +178,8 @@ module ApplicationHelper
def search_placeholder def search_placeholder
if @project && @project.persisted? if @project && @project.persisted?
"Search in this project" "Search in this project"
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted? elsif @group && @group.persisted?
"Search in this group" "Search in this group"
else else
......
...@@ -16,38 +16,6 @@ module CommitsHelper ...@@ -16,38 +16,6 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer)) commit_person_link(commit, options.merge(source: :committer))
end end
def each_diff_line(diff, index)
Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path)
.each do |full_line, type, line_code, line_new, line_old|
yield(full_line, type, line_code, line_new, line_old)
end
end
def each_diff_line_near(diff, index, expected_line_code)
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
each_diff_line(diff, index) do |full_line, type, line_code, line_new, line_old|
line = [full_line, type, line_code, line_new, line_old]
if line_code != expected_line_code
if type == "match"
prev_lines.clear
prev_match_line = line
else
prev_lines.push(line)
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
else
yield(prev_match_line) if !prev_match_line.nil?
prev_lines.each { |ln| yield(ln) }
yield(line)
break
end
end
end
def image_diff_class(diff) def image_diff_class(diff)
if diff.deleted_file if diff.deleted_file
"deleted" "deleted"
...@@ -63,14 +31,6 @@ module CommitsHelper ...@@ -63,14 +31,6 @@ module CommitsHelper
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil? escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
end end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
# Breadcrumb links for a Project and, if applicable, a tree path # Breadcrumb links for a Project and, if applicable, a tree path
def commits_breadcrumbs def commits_breadcrumbs
return unless @project && @ref return unless @project && @ref
...@@ -105,82 +65,6 @@ module CommitsHelper ...@@ -105,82 +65,6 @@ module CommitsHelper
branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
end end
def parallel_diff_lines(project, commit, diff, file)
old_file = project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
deleted_lines = {}
added_lines = {}
each_diff_line(diff, 0) do |line, type, line_code, line_new, line_old|
if type == "old"
deleted_lines[line_old] = { line_code: line_code, type: type, line: line }
elsif type == "new"
added_lines[line_new] = { line_code: line_code, type: type, line: line }
end
end
max_length = old_file ? [old_file.loc, file.loc].max : file.loc
offset1 = 0
offset2 = 0
old_lines = []
new_lines = []
max_length.times do |line_index|
line_index1 = line_index - offset1
line_index2 = line_index - offset2
deleted_line = deleted_lines[line_index1 + 1]
added_line = added_lines[line_index2 + 1]
old_line = old_file.lines[line_index1] if old_file
new_line = file.lines[line_index2]
if deleted_line && added_line
elsif deleted_line
new_line = nil
offset2 += 1
elsif added_line
old_line = nil
offset1 += 1
end
old_lines[line_index] = DiffLine.new
new_lines[line_index] = DiffLine.new
# old
if line_index == 0 && diff.new_file
old_lines[line_index].type = :file_created
old_lines[line_index].content = 'File was created'
elsif deleted_line
old_lines[line_index].type = :deleted
old_lines[line_index].content = old_line
old_lines[line_index].num = line_index1 + 1
old_lines[line_index].code = deleted_line[:line_code]
elsif old_line
old_lines[line_index].type = :no_change
old_lines[line_index].content = old_line
old_lines[line_index].num = line_index1 + 1
else
old_lines[line_index].type = :added
end
# new
if line_index == 0 && diff.deleted_file
new_lines[line_index].type = :file_deleted
new_lines[line_index].content = "File was deleted"
elsif added_line
new_lines[line_index].type = :added
new_lines[line_index].num = line_index2 + 1
new_lines[line_index].content = new_line
new_lines[line_index].code = added_line[:line_code]
elsif new_line
new_lines[line_index].type = :no_change
new_lines[line_index].num = line_index2 + 1
new_lines[line_index].content = new_line
else
new_lines[line_index].type = :deleted
end
end
return old_lines, new_lines
end
def link_to_browse_code(project, commit) def link_to_browse_code(project, commit)
if current_controller?(:projects, :commits) if current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path) if @repo.blob_at(commit.id, @path)
...@@ -229,14 +113,6 @@ module CommitsHelper ...@@ -229,14 +113,6 @@ module CommitsHelper
end end
end end
def diff_file_mode_changed?(diff)
diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def view_file_btn(commit_sha, diff, project) def view_file_btn(commit_sha, diff, project)
link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)), link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file' do class: 'btn btn-small view-file js-view-file' do
......
module DiffHelper module DiffHelper
def safe_diff_files(diffs) def allowed_diff_size
if diff_hard_limit_enabled? if diff_hard_limit_enabled?
diffs.first(Commit::DIFF_HARD_LIMIT_FILES) Commit::DIFF_HARD_LIMIT_FILES
else else
diffs.first(Commit::DIFF_SAFE_FILES) Commit::DIFF_SAFE_FILES
end
end
def safe_diff_files(diffs)
diffs.first(allowed_diff_size).map do |diff|
Gitlab::Diff::File.new(diff)
end end
end end
def show_diff_size_warninig?(diffs) def show_diff_size_warning?(diffs)
safe_diff_files(diffs).size < diffs.size diffs.size > allowed_diff_size
end end
def diff_hard_limit_enabled? def diff_hard_limit_enabled?
...@@ -19,4 +25,96 @@ module DiffHelper ...@@ -19,4 +25,96 @@ module DiffHelper
false false
end end
end end
def generate_line_code(file_path, line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def parallel_diff(diff_file, index)
lines = []
skip_next = false
# Building array of lines
#
# [
# left_type, left_line_number, left_line_content, left_line_code,
# right_line_type, right_line_number, right_line_content, right_line_code
# ]
#
diff_file.diff_lines.each do |line|
full_line = line.text
type = line.type
line_code = generate_line_code(diff_file.file_path, line)
line_new = line.new_pos
line_old = line.old_pos
next_line = diff_file.next_line(line.index)
if next_line
next_line_code = generate_line_code(diff_file.file_path, next_line)
next_type = next_line.type
next_line = next_line.text
end
if type == 'match' || type.nil?
# line in the right panel is the same as in the left one
line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code]
lines.push(line)
elsif type == 'old'
if next_type == 'new'
# Left side has text removed, right side has text added
line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code]
lines.push(line)
skip_next = true
elsif next_type == 'old' || next_type.nil?
# Left side has text removed, right side doesn't have any change
# No next line code, no new line number, no new line text
line = [type, line_old, full_line, line_code, next_type, nil, "&nbsp;", nil]
lines.push(line)
end
elsif type == 'new'
if skip_next
# Change has been already included in previous line so no need to do it again
skip_next = false
next
else
# Change is only on the right side, left side has no change
line = [nil, nil, "&nbsp;", line_code, type, line_new, full_line, line_code]
lines.push(line)
end
end
end
lines
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
def line_comments
@line_comments ||= @line_notes.group_by(&:line_code)
end
def organize_comments(type_left, type_right, line_code_left, line_code_right)
comments_left = comments_right = nil
unless type_left.nil? && type_right == 'new'
comments_left = line_comments[line_code_left]
end
unless type_left.nil? && type_right.nil?
comments_right = line_comments[line_code_right]
end
[comments_left, comments_right]
end
end end
...@@ -52,6 +52,8 @@ module EventsHelper ...@@ -52,6 +52,8 @@ module EventsHelper
"#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}" "#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
elsif event.membership_changed? elsif event.membership_changed?
"#{event.author_name} #{event.action_name} #{event.project_name}" "#{event.author_name} #{event.action_name} #{event.project_name}"
elsif event.note? && event.note_commit?
"#{event.author_name} commented on #{event.note_target_type} #{event.note_short_commit_id} at #{event.project_name}"
elsif event.note? elsif event.note?
"#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}" "#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}"
else else
...@@ -64,6 +66,8 @@ module EventsHelper ...@@ -64,6 +66,8 @@ module EventsHelper
project_issue_url(event.project, event.issue) project_issue_url(event.project, event.issue)
elsif event.merge_request? elsif event.merge_request?
project_merge_request_url(event.project, event.merge_request) project_merge_request_url(event.project, event.merge_request)
elsif event.note? && event.note_commit?
project_commit_url(event.project, event.note_target)
elsif event.note? elsif event.note?
if event.note_target if event.note_target
if event.note_commit? if event.note_commit?
...@@ -94,6 +98,8 @@ module EventsHelper ...@@ -94,6 +98,8 @@ module EventsHelper
render "events/event_push", event: event render "events/event_push", event: event
elsif event.merge_request? elsif event.merge_request?
render "events/event_merge_request", merge_request: event.merge_request render "events/event_merge_request", merge_request: event.merge_request
elsif event.push?
render "events/event_push", event: event
elsif event.note? elsif event.note?
render "events/event_note", note: event.note render "events/event_note", note: event.note
end end
......
...@@ -123,7 +123,7 @@ module ProjectsHelper ...@@ -123,7 +123,7 @@ module ProjectsHelper
end end
def link_to_toggle_star(title, starred, signed_in) def link_to_toggle_star(title, starred, signed_in)
cls = 'btn btn-block' cls = 'star-btn'
cls += ' disabled' unless signed_in cls += ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do toggle_html = content_tag('span', class: 'toggle') do
...@@ -151,7 +151,7 @@ module ProjectsHelper ...@@ -151,7 +151,7 @@ module ProjectsHelper
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
link_to toggle_star_project_path(@project), link_opts do link_to toggle_star_project_path(@project), link_opts do
toggle_html + count_html toggle_html + ' ' + count_html
end end
end end
end end
...@@ -261,4 +261,10 @@ module ProjectsHelper ...@@ -261,4 +261,10 @@ module ProjectsHelper
project_blob_path(project, tree_join(project.default_branch, project.repository.contribution_guide.name)) project_blob_path(project, tree_join(project.default_branch, project.repository.contribution_guide.name))
end end
end end
def hidden_pass_url(original_url)
result = URI(original_url)
result.password = '*****' if result.password.present?
result
end
end end
...@@ -91,4 +91,21 @@ module SearchHelper ...@@ -91,4 +91,21 @@ module SearchHelper
def search_result_sanitize(str) def search_result_sanitize(str)
Sanitize.clean(str) Sanitize.clean(str)
end end
def search_filter_path(options={})
exist_opts = {
search: params[:search],
project_id: params[:project_id],
group_id: params[:group_id],
scope: params[:scope]
}
options = exist_opts.merge(options)
search_path(options)
end
# Sanitize html generated after parsing markdown from issue description or comment
def search_md_sanitize(html)
sanitize(html, tags: %w(a p ol ul li pre code))
end
end end
...@@ -49,6 +49,10 @@ module Issuable ...@@ -49,6 +49,10 @@ module Issuable
where("LOWER(title) like :query", query: "%#{query.downcase}%") where("LOWER(title) like :query", query: "%#{query.downcase}%")
end end
def full_search(query)
where("LOWER(title) like :query OR LOWER(description) like :query", query: "%#{query.downcase}%")
end
def sort(method) def sort(method)
case method.to_s case method.to_s
when 'newest' then reorder("#{table_name}.created_at DESC") when 'newest' then reorder("#{table_name}.created_at DESC")
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
# milestone_id :integer # milestone_id :integer
# state :string(255) # state :string(255)
# iid :integer # iid :integer
# attachment :string(255)
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
......
...@@ -31,7 +31,9 @@ class Key < ActiveRecord::Base ...@@ -31,7 +31,9 @@ class Key < ActiveRecord::Base
after_create :add_to_shell after_create :add_to_shell
after_create :notify_user after_create :notify_user
after_create :post_create_hook
after_destroy :remove_from_shell after_destroy :remove_from_shell
after_destroy :post_destroy_hook
def strip_white_space def strip_white_space
self.key = key.strip unless key.blank? self.key = key.strip unless key.blank?
...@@ -58,6 +60,10 @@ class Key < ActiveRecord::Base ...@@ -58,6 +60,10 @@ class Key < ActiveRecord::Base
NotificationService.new.new_key(self) NotificationService.new.new_key(self)
end end
def post_create_hook
SystemHooksService.new.execute_hooks_for(self, :create)
end
def remove_from_shell def remove_from_shell
GitlabShellWorker.perform_async( GitlabShellWorker.perform_async(
:remove_key, :remove_key,
...@@ -66,6 +72,10 @@ class Key < ActiveRecord::Base ...@@ -66,6 +72,10 @@ class Key < ActiveRecord::Base
) )
end end
def post_destroy_hook
SystemHooksService.new.execute_hooks_for(self, :destroy)
end
private private
def generate_fingerpint def generate_fingerpint
......
# == Schema Information
#
# Table name: labels
#
# id :integer not null, primary key
# title :string(255)
# color :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
#
class Label < ActiveRecord::Base class Label < ActiveRecord::Base
DEFAULT_COLOR = '#428BCA' DEFAULT_COLOR = '#428BCA'
......
# == Schema Information
#
# Table name: label_links
#
# id :integer not null, primary key
# label_id :integer
# target_id :integer
# target_type :string(255)
# created_at :datetime
# updated_at :datetime
#
class LabelLink < ActiveRecord::Base class LabelLink < ActiveRecord::Base
belongs_to :target, polymorphic: true belongs_to :target, polymorphic: true
belongs_to :label belongs_to :label
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
# target_project_id :integer not null # target_project_id :integer not null
# iid :integer # iid :integer
# description :text # description :text
# position :integer default(0)
# #
require Rails.root.join("app/models/commit") require Rails.root.join("app/models/commit")
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Table name: merge_request_diffs # Table name: merge_request_diffs
# #
# id :integer not null, primary key # id :integer not null, primary key
# state :string(255) default("collected"), not null # state :string(255)
# st_commits :text # st_commits :text
# st_diffs :text # st_diffs :text
# merge_request_id :integer not null # merge_request_id :integer not null
......
...@@ -178,12 +178,6 @@ module Network ...@@ -178,12 +178,6 @@ module Network
space = find_free_space(time_range, 2, space_base) space = find_free_space(time_range, 2, space_base)
leaves.each do |l| leaves.each do |l|
l.spaces << space l.spaces << space
# Also add space to parent
l.parents(@map).each do |parent|
if 0 < parent.space && parent.space < space
parent.spaces << space
end
end
end end
# and mark it as reserved # and mark it as reserved
......
...@@ -117,6 +117,25 @@ class Note < ActiveRecord::Base ...@@ -117,6 +117,25 @@ class Note < ActiveRecord::Base
}) })
end end
def create_new_commits_note(noteable, project, author, commits)
commits_text = ActionController::Base.helpers.pluralize(commits.size, 'new commit')
body = "Added #{commits_text}:\n\n"
commits.each do |commit|
message = "* #{commit.short_id} - #{commit.title}"
body << message
body << "\n"
end
create(
noteable: noteable,
project: project,
author: author,
note: body,
system: true
)
end
def discussions_from_notes(notes) def discussions_from_notes(notes)
discussion_ids = [] discussion_ids = []
discussions = [] discussions = []
...@@ -146,6 +165,10 @@ class Note < ActiveRecord::Base ...@@ -146,6 +165,10 @@ class Note < ActiveRecord::Base
def cross_reference_exists?(noteable, mentioner) def cross_reference_exists?(noteable, mentioner)
where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any? where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any?
end end
def search(query)
where("note like :query", query: "%#{query}%")
end
end end
def commit_author def commit_author
...@@ -186,9 +209,10 @@ class Note < ActiveRecord::Base ...@@ -186,9 +209,10 @@ class Note < ActiveRecord::Base
noteable.diffs.each do |mr_diff| noteable.diffs.each do |mr_diff|
next unless mr_diff.new_path == self.diff.new_path next unless mr_diff.new_path == self.diff.new_path
Gitlab::DiffParser.new(mr_diff.diff.lines.to_a, mr_diff.new_path). lines = Gitlab::Diff::Parser.new.parse(mr_diff.diff.lines.to_a)
each do |full_line, type, line_code, line_new, line_old|
if full_line == diff_line lines.each do |line|
if line.text == diff_line
return true return true
end end
end end
...@@ -209,6 +233,14 @@ class Note < ActiveRecord::Base ...@@ -209,6 +233,14 @@ class Note < ActiveRecord::Base
diff.new_path if diff diff.new_path if diff
end end
def file_path
if diff.new_path.present?
diff.new_path
elsif diff.old_path.present?
diff.old_path
end
end
def diff_old_line def diff_old_line
line_code.split('_')[1].to_i line_code.split('_')[1].to_i
end end
...@@ -217,19 +249,49 @@ class Note < ActiveRecord::Base ...@@ -217,19 +249,49 @@ class Note < ActiveRecord::Base
line_code.split('_')[2].to_i line_code.split('_')[2].to_i
end end
def generate_line_code(line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def diff_line def diff_line
return @diff_line if @diff_line return @diff_line if @diff_line
if diff if diff
Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path) diff_lines.each do |line|
.each do |full_line, type, line_code, line_new, line_old| if generate_line_code(line) == self.line_code
@diff_line = full_line if line_code == self.line_code @diff_line = line.text
end end
end
end end
@diff_line @diff_line
end end
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
diff_lines.each do |line|
if generate_line_code(line) != self.line_code
if line.type == "match"
prev_lines.clear
prev_match_line = line
else
prev_lines.push(line)
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
else
prev_lines << line
return prev_lines
end
end
end
def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a)
end
def discussion_id def discussion_id
@discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code) @discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
end end
...@@ -333,4 +395,8 @@ class Note < ActiveRecord::Base ...@@ -333,4 +395,8 @@ class Note < ActiveRecord::Base
def set_references def set_references
notice_added_references(project, author) notice_added_references(project, author)
end end
def editable?
!system
end
end end
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
# visibility_level :integer default(0), not null # visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null # archived :boolean default(FALSE), not null
# import_status :string(255) # import_status :string(255)
# star_count :integer # repository_size :float default(0.0)
# star_count :integer default(0), not null
# #
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
...@@ -73,7 +74,7 @@ class Project < ActiveRecord::Base ...@@ -73,7 +74,7 @@ class Project < ActiveRecord::Base
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id" has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
# Merge requests from source project should be kept when source project was removed # Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy
has_many :labels, dependent: :destroy has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy has_many :services, dependent: :destroy
has_many :events, dependent: :destroy has_many :events, dependent: :destroy
...@@ -183,11 +184,11 @@ class Project < ActiveRecord::Base ...@@ -183,11 +184,11 @@ class Project < ActiveRecord::Base
joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC") joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
end end
def search query def search(query)
joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%")
end end
def search_by_title query def search_by_title(query)
where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%") where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%")
end end
...@@ -414,18 +415,35 @@ class Project < ActiveRecord::Base ...@@ -414,18 +415,35 @@ class Project < ActiveRecord::Base
def update_merge_requests(oldrev, newrev, ref, user) def update_merge_requests(oldrev, newrev, ref, user)
return true unless ref =~ /heads/ return true unless ref =~ /heads/
branch_name = ref.gsub("refs/heads/", "") branch_name = ref.gsub("refs/heads/", "")
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id) commits = self.repository.commits_between(oldrev, newrev)
c_ids = commits.map(&:id)
# Close merge requests # Close merge requests
mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) }
mrs.uniq.each do |merge_request|
MergeRequests::MergeService.new.execute(merge_request, user, nil)
end
# Update code for merge requests into project between project branches # Update code for merge requests into project between project branches
mrs = self.merge_requests.opened.by_branch(branch_name).to_a mrs = self.merge_requests.opened.by_branch(branch_name).to_a
# Update code for merge requests between project and project fork # Update code for merge requests between project and project fork
mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
mrs.uniq.each do |merge_request|
merge_request.reload_code
merge_request.mark_as_unchecked
end
# Add comment about pushing new commits to merge requests
mrs = self.merge_requests.opened.where(source_branch: branch_name).to_a
mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a
mrs.uniq.each do |merge_request|
Note.create_new_commits_note(merge_request, merge_request.project,
user, commits)
end
true true
end end
......
...@@ -5,21 +5,17 @@ ...@@ -5,21 +5,17 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
class AssemblaService < Service class AssemblaService < Service
include HTTParty include HTTParty
prop_accessor :token, :subdomain
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
......
...@@ -5,19 +5,15 @@ ...@@ -5,19 +5,15 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
class CampfireService < Service class CampfireService < Service
prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
# Base class for CI services # Base class for CI services
# List methods you need to implement to get your CI service # List methods you need to implement to get your CI service
# working with GitLab Merge Requests # working with GitLab Merge Requests
......
...@@ -5,19 +5,15 @@ ...@@ -5,19 +5,15 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
class EmailsOnPushService < Service class EmailsOnPushService < Service
prop_accessor :recipients
validates :recipients, presence: true, if: :activated? validates :recipients, presence: true, if: :activated?
def title def title
......
...@@ -5,21 +5,17 @@ ...@@ -5,21 +5,17 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
require "flowdock-git-hook" require "flowdock-git-hook"
class FlowdockService < Service class FlowdockService < Service
prop_accessor :token
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
......
...@@ -5,21 +5,17 @@ ...@@ -5,21 +5,17 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
require "gemnasium/gitlab_service" require "gemnasium/gitlab_service"
class GemnasiumService < Service class GemnasiumService < Service
prop_accessor :token, :api_key
validates :token, :api_key, presence: true, if: :activated? validates :token, :api_key, presence: true, if: :activated?
def title def title
......
...@@ -5,19 +5,15 @@ ...@@ -5,19 +5,15 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # property :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
class GitlabCiService < CiService class GitlabCiService < CiService
prop_accessor :project_url, :token
validates :project_url, presence: true, if: :activated? validates :project_url, presence: true, if: :activated?
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
......
...@@ -5,21 +5,17 @@ ...@@ -5,21 +5,17 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
class HipchatService < Service class HipchatService < Service
MAX_COMMITS = 3 MAX_COMMITS = 3
prop_accessor :token, :room
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
......
...@@ -5,19 +5,16 @@ ...@@ -5,19 +5,16 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
class JenkinsService < CiService class JenkinsService < CiService
prop_accessor :project_url
validates :project_url, presence: true, if: :activated? validates :project_url, presence: true, if: :activated?
delegate :execute, to: :service_hook, prefix: nil delegate :execute, to: :service_hook, prefix: nil
......
...@@ -5,23 +5,18 @@ ...@@ -5,23 +5,18 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255) #
# room :string(255)
# recipients :text
# api_key :string(255)
# username :string(255)
# password :string(255)
# api_version :string(255)
class JiraService < Service class JiraService < Service
include HTTParty include HTTParty
prop_accessor :project_url, :username, :password,
:api_version, :jira_issue_transition_id
validates :username, :password, presence: true, if: :activated? validates :username, :password, presence: true, if: :activated?
before_validation :set_api_version before_validation :set_api_version
......
...@@ -5,21 +5,17 @@ ...@@ -5,21 +5,17 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
class PivotaltrackerService < Service class PivotaltrackerService < Service
include HTTParty include HTTParty
prop_accessor :token
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
......
...@@ -5,19 +5,15 @@ ...@@ -5,19 +5,15 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# #
class SlackService < Service class SlackService < Service
prop_accessor :room, :subdomain, :token
validates :room, presence: true, if: :activated? validates :room, presence: true, if: :activated?
validates :subdomain, presence: true, if: :activated? validates :subdomain, presence: true, if: :activated?
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
......
...@@ -2,8 +2,9 @@ class ProjectWiki ...@@ -2,8 +2,9 @@ class ProjectWiki
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
MARKUPS = { MARKUPS = {
"Markdown" => :markdown, 'Markdown' => :markdown,
"RDoc" => :rdoc 'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc
} }
class CouldNotCreateWikiError < StandardError; end class CouldNotCreateWikiError < StandardError; end
......
...@@ -64,10 +64,10 @@ class Repository ...@@ -64,10 +64,10 @@ class Repository
gitlab_shell.add_branch(path_with_namespace, branch_name, ref) gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
end end
def add_tag(tag_name, ref) def add_tag(tag_name, ref, message = nil)
Rails.cache.delete(cache_key(:tag_names)) Rails.cache.delete(cache_key(:tag_names))
gitlab_shell.add_tag(path_with_namespace, tag_name, ref) gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end end
def rm_branch(branch_name) def rm_branch(branch_name)
......
...@@ -5,23 +5,21 @@ ...@@ -5,23 +5,21 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# token :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# project_url :string(255) # properties :text
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
#
# To add new service you should build a class inherited from Service # To add new service you should build a class inherited from Service
# and implement a set of methods # and implement a set of methods
class Service < ActiveRecord::Base class Service < ActiveRecord::Base
serialize :properties, JSON
default_value_for :active, false default_value_for :active, false
after_initialize :initialize_properties
belongs_to :project belongs_to :project
has_one :service_hook has_one :service_hook
...@@ -35,6 +33,10 @@ class Service < ActiveRecord::Base ...@@ -35,6 +33,10 @@ class Service < ActiveRecord::Base
:common :common
end end
def initialize_properties
self.properties = {} if properties.nil?
end
def title def title
# implement inside child # implement inside child
end end
...@@ -63,4 +65,20 @@ class Service < ActiveRecord::Base ...@@ -63,4 +65,20 @@ class Service < ActiveRecord::Base
def can_test? def can_test?
!project.empty_repo? !project.empty_repo?
end end
# Provide convenient accessor methods
# for each serialized property.
def self.prop_accessor(*args)
args.each do |arg|
class_eval %{
def #{arg}
properties['#{arg}']
end
def #{arg}=(value)
self.properties['#{arg}'] = value
end
}
end
end
end end
...@@ -65,4 +65,18 @@ class Snippet < ActiveRecord::Base ...@@ -65,4 +65,18 @@ class Snippet < ActiveRecord::Base
def expired? def expired?
expires_at && expires_at < Time.current expires_at && expires_at < Time.current
end end
class << self
def search(query)
where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%")
end
def search_code(query)
where('(content LIKE :query)', query: "%#{query}%")
end
def accessible_to(user)
where('private = ? OR author_id = ?', false, user)
end
end
end end
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
# #
# Table name: users_star_projects # Table name: users_star_projects
# #
# id :integer not null, primary key # id :integer not null, primary key
# starrer_id :integer not null # project_id :integer not null
# project_id :integer not null # user_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# #
class UsersStarProject < ActiveRecord::Base class UsersStarProject < ActiveRecord::Base
......
...@@ -23,7 +23,7 @@ class WebHook < ActiveRecord::Base ...@@ -23,7 +23,7 @@ class WebHook < ActiveRecord::Base
default_value_for :merge_requests_events, false default_value_for :merge_requests_events, false
# HTTParty timeout # HTTParty timeout
default_timeout 10 default_timeout Gitlab.config.gitlab.webhook_timeout
validates :url, presence: true, validates :url, presence: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" } format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
......
...@@ -4,8 +4,8 @@ class CompareService ...@@ -4,8 +4,8 @@ class CompareService
def execute(current_user, source_project, source_branch, target_project, target_branch) def execute(current_user, source_project, source_branch, target_project, target_branch)
# Try to compare branches to get commits list and diffs # Try to compare branches to get commits list and diffs
# #
# Note: Use satellite only when need to compare between to repos # Note: Use satellite only when need to compare between two repos
# because satellites are slower then operations on bare repo # because satellites are slower than operations on bare repo
if target_project == source_project if target_project == source_project
Gitlab::CompareResult.new( Gitlab::CompareResult.new(
Gitlab::Git::Compare.new( Gitlab::Git::Compare.new(
......
class CreateBranchService class CreateBranchService
def execute(project, branch_name, ref, current_user) def execute(project, branch_name, ref, current_user)
valid_branch = Gitlab::GitRefValidator.validate(branch_name)
if valid_branch == false
return error('Branch name invalid')
end
repository = project.repository repository = project.repository
existing_branch = repository.find_branch(branch_name)
if existing_branch
return error('Branch already exists')
end
repository.add_branch(branch_name, ref) repository.add_branch(branch_name, ref)
new_branch = repository.find_branch(branch_name) new_branch = repository.find_branch(branch_name)
if new_branch if new_branch
Event.create_ref_event(project, current_user, new_branch, 'add') Event.create_ref_event(project, current_user, new_branch, 'add')
return success(new_branch)
else
return error('Invalid reference name')
end end
end
def error(message)
{
message: message,
status: :error
}
end
new_branch def success(branch)
{
branch: branch,
status: :success
}
end end
end end
class CreateTagService class CreateTagService
def execute(project, tag_name, ref, current_user) def execute(project, tag_name, ref, message, current_user)
valid_tag = Gitlab::GitRefValidator.validate(tag_name)
if valid_tag == false
return error('Tag name invalid')
end
repository = project.repository repository = project.repository
repository.add_tag(tag_name, ref) existing_tag = repository.find_tag(tag_name)
if existing_tag
return error('Tag already exists')
end
if message
message.gsub!(/^\s+|\s+$/, '')
end
repository.add_tag(tag_name, ref, message)
new_tag = repository.find_tag(tag_name) new_tag = repository.find_tag(tag_name)
if new_tag if new_tag
Event.create_ref_event(project, current_user, new_tag, 'add', 'refs/tags') Event.create_ref_event(project, current_user, new_tag, 'add', 'refs/tags')
return success(new_tag)
else
return error('Invalid reference name')
end end
end
def error(message)
{
message: message,
status: :error
}
end
new_tag def success(branch)
{
tag: branch,
status: :success
}
end end
end end
...@@ -5,21 +5,21 @@ class DeleteBranchService ...@@ -5,21 +5,21 @@ class DeleteBranchService
# No such branch # No such branch
unless branch unless branch
return error('No such branch') return error('No such branch', 404)
end end
if branch_name == repository.root_ref if branch_name == repository.root_ref
return error('Cannot remove HEAD branch') return error('Cannot remove HEAD branch', 405)
end end
# Dont allow remove of protected branch # Dont allow remove of protected branch
if project.protected_branch?(branch_name) if project.protected_branch?(branch_name)
return error('Protected branch cant be removed') return error('Protected branch cant be removed', 405)
end end
# Dont allow user to remove branch if he is not allowed to push # Dont allow user to remove branch if he is not allowed to push
unless current_user.can?(:push_code, project) unless current_user.can?(:push_code, project)
return error('You dont have push access to repo') return error('You dont have push access to repo', 405)
end end
if repository.rm_branch(branch_name) if repository.rm_branch(branch_name)
...@@ -30,9 +30,10 @@ class DeleteBranchService ...@@ -30,9 +30,10 @@ class DeleteBranchService
end end
end end
def error(message) def error(message, return_code = 400)
{ {
message: message, message: message,
return_code: return_code,
state: :error state: :error
} }
end end
......
...@@ -7,7 +7,12 @@ module Projects ...@@ -7,7 +7,12 @@ module Projects
end end
def execute def execute
project = @from_project.dup project_params = {
visibility_level: @from_project.visibility_level,
description: @from_project.description,
}
project = Project.new(project_params)
project.name = @from_project.name project.name = @from_project.name
project.path = @from_project.path project.path = @from_project.path
project.namespace = current_user.namespace project.namespace = current_user.namespace
......
...@@ -7,30 +7,12 @@ module Search ...@@ -7,30 +7,12 @@ module Search
end end
def execute def execute
query = params[:search]
query = Shellwords.shellescape(query) if query.present?
return result unless query.present?
group = Group.find_by(id: params[:group_id]) if params[:group_id].present? group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new.execute(current_user) projects = ProjectsFinder.new.execute(current_user)
projects = projects.where(namespace_id: group.id) if group projects = projects.where(namespace_id: group.id) if group
project_ids = projects.pluck(:id) project_ids = projects.pluck(:id)
result[:projects] = projects.search(query).limit(20) Gitlab::SearchResults.new(project_ids, params[:search])
result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).order('updated_at DESC').limit(20)
result[:issues] = Issue.where(project_id: project_ids).search(query).order('updated_at DESC').limit(20)
result[:total_results] = %w(projects issues merge_requests).sum { |items| result[items.to_sym].size }
result
end
def result
@result ||= {
projects: [],
merge_requests: [],
issues: [],
notes: [],
total_results: 0,
}
end end
end end
end end
...@@ -7,39 +7,9 @@ module Search ...@@ -7,39 +7,9 @@ module Search
end end
def execute def execute
query = params[:search] Gitlab::ProjectSearchResults.new(project.id,
query = Shellwords.shellescape(query) if query.present? params[:search],
return result unless query.present? params[:repository_ref])
if params[:search_code].present?
if !@project.empty_repo?
blobs = project.repository.search_files(query,
params[:repository_ref])
else
blobs = Array.new
end
blobs = Kaminari.paginate_array(blobs).page(params[:page]).per(20)
result[:blobs] = blobs
result[:total_results] = blobs.total_count
else
result[:merge_requests] = project.merge_requests.search(query).order('updated_at DESC').limit(20)
result[:issues] = project.issues.where("title like :query OR description like :query ", query: "%#{query}%").order('updated_at DESC').limit(20)
result[:notes] = Note.where(noteable_type: 'issue').where(project_id: project.id).where("note like :query", query: "%#{query}%").order('updated_at DESC').limit(20)
result[:total_results] = %w(issues merge_requests notes).sum { |items| result[items.to_sym].size }
end
result
end
def result
@result ||= {
merge_requests: [],
issues: [],
blobs: [],
notes: [],
total_results: 0,
}
end end
end end
end end
module Search
class SnippetService
attr_accessor :current_user, :params
def initialize(user, params)
@current_user, @params = user, params.dup
end
def execute
snippet_ids = Snippet.accessible_to(current_user).pluck(:id)
Gitlab::SnippetSearchResults.new(snippet_ids, params[:search])
end
end
end
...@@ -22,6 +22,16 @@ class SystemHooksService ...@@ -22,6 +22,16 @@ class SystemHooksService
} }
case model case model
when Key
data.merge!(
key: model.key,
id: model.id
)
if model.user
data.merge!(
username: model.user.username
)
end
when Project when Project
owner = model.owner owner = model.owner
......
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,7 @@
%span.light.pull-right %span.light.pull-right
= Milestone.count = Milestone.count
%p %p
Monthly active users Active users last 30 days
%span.light.pull-right %span.light.pull-right
= User.where("current_sign_in_at > ?", 30.days.ago).count = User.where("current_sign_in_at > ?", 30.days.ago).count
.col-md-4 .col-md-4
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment