Commit 4df15fe3 authored by Sytse Sijbrandij's avatar Sytse Sijbrandij

Merge branch 'master' into gitlab-flow

Conflicts:
	CONTRIBUTING.md
parents 6f61cbd1 2ebd2d41
......@@ -38,3 +38,4 @@ public/uploads.*
public/assets/
.envrc
dump.rdb
tags
......@@ -5,6 +5,8 @@ targets:
debian-7: &wheezy
build_dependencies:
- libicu-dev
- cmake
- pkg-config
dependencies:
- libicu48
- libpcre3
......@@ -13,6 +15,8 @@ targets:
ubuntu-14.04:
build_dependencies:
- libicu-dev
- cmake
- pkg-config
dependencies:
- libicu52
- libpcre3
......@@ -20,6 +24,8 @@ targets:
centos-6:
build_dependencies:
- libicu-devel
- cmake
- pkgconfig
dependencies:
- libicu
- 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.4.0
- Refactored membership logic
- Fix creating new files with web editor
- Improve error reporting on users API (Julien Bianchi)
- Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally
- Increase unicorn timeout to 60 seconds
- Sort search autocomplete projects by stars count so most popular go first
v 7.3.1
- Fix ref parsing in Gitlab::GitAccess
- Fix error 500 when viewing diff on a file with changed permissions
- Fix adding comments to MR when source branch is master
- Fix error 500 when searching description contains relative link
v 7.3.0
- Always set the 'origin' remote in satellite actions
- Write authorized_keys in tmp/ during tests
- Use sockets to connect to Redis
- Add dormant New Relic gem (can be enabled via environment variables)
- Expire Rack sessions after 1 week
- Cleaner signin/signup pages
- Improved comments UI
- Better search with filtering, pagination etc
- Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov)
- Prevent project stars duplication when fork project
- Use the default Unicorn socket backlog value of 1024
- Support Unix domain sockets for Redis
- Store session Redis keys in 'session:gitlab:' namespace
- Deprecate LDAP account takeover based on partial LDAP email / GitLab username match
- Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy)
- Keyboard shortcuts for productivity (Robert Schilling)
- API: filter issues by state (Julien Bianchi)
- API: filter issues by labels (Julien Bianchi)
- Add system hook for ssh key changes
- Add blob permalink link (Ciro Santilli)
- Create annotated tags through UI and API (Sean Edge)
- Snippets search (Charles Bushong)
- Comment new push to existing MR
- Add 'ci' to the blacklist of forbidden names
- Improve text filtering on issues page
- Comment & Close button
- Process git push --all much faster
- Don't allow edit of system notes
- Project wiki search (Ralf Seidler)
- Enabled Shibboleth authentication support (Matus Banas)
- Zen mode (fullscreen) for issues/MR/notes (Robert Schilling)
- Add ability to configure webhook timeout via gitlab.yml (Wes Gurney)
- Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media)
- Add Redis socket support to 'rake gitlab:shell:install'
v 7.2.1
- Delete orphaned labels during label migration (James Brooks)
- Security: prevent XSS with stricter MIME types for raw repo files
v 7.2.0
- Explore page
......
......@@ -44,7 +44,7 @@ Please send a merge request with a tested solution or a merge request with a fai
1. **Output of checks**
* Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing
* Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md)
* Add the last commit sha1 of the GitLab version you used to replicate the issue (obtainable from the help page)
* Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page)
* Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem
......@@ -62,6 +62,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG) insert your line at a [random point](doc/workflow/gitlab_flow.md#do-not-order-commits-with-rebase) in the current version
1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
1. Submit a merge request (MR) to the master branch
......@@ -75,7 +76,7 @@ If you can, please submit a merge request with the fix or improvements including
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a mimimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria.
......@@ -85,19 +86,19 @@ For examples of feedback on merge requests please look at already [closed merge
1. Are there points in the code the reviewer needs to double check?
1. Why was this MR needed?
1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
1. Screenshots (If appropiate)
1. Screenshots (If appropriate)
## Contribution acceptance criteria
1. The change is as small as possible (see the above paragraph for details)
1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
1. Can merge without problems (if not please use: `git rebase master`)
1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
1. Does not break any existing functionality
1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
1. It conforms to the following style guides
......@@ -114,4 +115,4 @@ For examples of feedback on merge requests please look at already [closed merge
1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
This is also the style used by linting tools such as [Rubocop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
......@@ -27,16 +27,17 @@ gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
gem 'omniauth-shibboleth'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 6.0'
gem "gitlab_git", '7.0.0.rc3'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth
gem 'gitlab_omniauth-ldap', '1.0.4', require: "omniauth-ldap"
gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
# Git Wiki
gem 'gollum-lib', '~> 3.0.0'
......@@ -82,7 +83,7 @@ gem "seed-fu"
gem "github-markup"
# Required markup gems by github-markdown
gem 'redcarpet', '~> 2.2.2'
gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
gem 'org-ruby'
......@@ -156,6 +157,9 @@ gem "rack-attack"
# Ace editor
gem 'ace-rails-ap'
# Keyboard shortcuts
gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar
gem 'semantic-ui-sass', '~> 0.16.1.0'
......@@ -231,7 +235,7 @@ group :development, :test do
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-spinach", '1.0.0'
end
......@@ -247,3 +251,5 @@ end
group :production do
gem "gitlab_meta", '7.0'
end
gem "newrelic_rpm"
......@@ -106,7 +106,7 @@ GEM
devise (~> 3.2)
diff-lcs (1.2.5)
diffy (3.0.3)
docile (1.1.1)
docile (1.1.5)
dotenv (0.9.0)
dropzonejs-rails (0.4.14)
rails (> 3.1)
......@@ -168,7 +168,7 @@ GEM
multi_json
gitlab-grack (2.0.0.pre)
rack (~> 1.5.1)
gitlab-grit (2.6.10)
gitlab-grit (2.6.11)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
......@@ -179,15 +179,15 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1)
gitlab_git (6.2.1)
gitlab_git (7.0.0.rc3)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-grit (~> 2.6)
gitlab-linguist (~> 3.0)
rubyzip (~> 1.1)
rugged (~> 0.21.0)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.0.4)
net-ldap (~> 0.3.1)
gitlab_omniauth-ldap (1.1.0)
net-ldap (~> 0.7.0)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
......@@ -287,14 +287,16 @@ GEM
mime-types (1.25.1)
mini_portile (0.6.0)
minitest (5.3.5)
mousetrap-rails (1.4.6)
multi_json (1.10.1)
multi_xml (0.5.5)
multipart-post (1.2.0)
mysql2 (0.3.16)
net-ldap (0.3.1)
net-ldap (0.7.0)
net-scp (1.1.2)
net-ssh (>= 2.6.5)
net-ssh (2.8.0)
newrelic_rpm (3.9.4.245)
nokogiri (1.6.2.1)
mini_portile (= 0.6.0)
nprogress-rails (0.1.2.3)
......@@ -320,6 +322,8 @@ GEM
omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
omniauth-shibboleth (1.1.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
......@@ -391,7 +395,7 @@ GEM
ffi (>= 0.5.0)
rdoc (3.12.2)
json (~> 1.4)
redcarpet (2.2.2)
redcarpet (3.1.2)
redis (3.0.6)
redis-actionpack (4.0.0)
actionpack (~> 4)
......@@ -436,6 +440,7 @@ GEM
ruby-progressbar (1.2.0)
rubyntlm (0.1.1)
rubypants (0.2.0)
rubyzip (1.1.6)
rugged (0.21.0)
safe_yaml (0.9.7)
sanitize (2.1.0)
......@@ -467,7 +472,7 @@ GEM
redis (>= 3.0.4)
redis-namespace (>= 1.3.1)
simple_oauth (0.1.9)
simplecov (0.8.2)
simplecov (0.9.0)
docile (~> 1.1.0)
multi_json
simplecov-html (~> 0.8.0)
......@@ -489,7 +494,7 @@ GEM
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
spring (1.1.1)
spring (1.1.3)
spring-commands-rspec (1.0.1)
spring (>= 0.9.1)
spring-commands-spinach (1.0.0)
......@@ -613,9 +618,9 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1)
gitlab_git (~> 6.0)
gitlab_git (= 7.0.0.rc3)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.0.4)
gitlab_omniauth-ldap (= 1.1.0)
gollum-lib (~> 3.0.0)
gon (~> 5.0.0)
grape (~> 0.6.1)
......@@ -636,11 +641,14 @@ DEPENDENCIES
launchy
letter_opener
minitest (~> 5.3.0)
mousetrap-rails
mysql2
newrelic_rpm
nprogress-rails
omniauth (~> 1.1.3)
omniauth-github
omniauth-google-oauth2
omniauth-shibboleth
omniauth-twitter
org-ruby
pg
......@@ -657,7 +665,7 @@ DEPENDENCIES
rb-fsevent
rb-inotify
rdoc (~> 3.6)
redcarpet (~> 2.2.2)
redcarpet (~> 3.1.2)
redis-rails
request_store
rspec-rails
......@@ -676,7 +684,7 @@ DEPENDENCIES
slack-notifier (~> 0.3.2)
slim
spinach-rails
spring (= 1.1.1)
spring (= 1.1.3)
spring-commands-rspec (= 1.0.1)
spring-commands-spinach (= 1.0.0)
stamp
......
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_pass: false do
guard 'rspec', cmd: "spring rspec", all_on_start: false, all_after_pass: false do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
......@@ -19,7 +19,7 @@ guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_p
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
end
guard 'spinach' do
guard 'spinach', command_prefix: 'spring' do
watch(%r|^features/(.*)\.feature|)
watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
"features/#{m[1]}#{m[2]}.feature"
......
......@@ -87,7 +87,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea
### Issue fixed in newer version
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://github.com/gitlabhq/gitlabhq/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Improperly formatted merge request
......
# GitLab
# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
## Open source software to collaborate on code
![logo](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/gitlab_logo.png)
![animated-screenshots](https://gist.github.com/fnkr/2f9badd56bfe0ed04ee7/raw/4f48806fbae97f556c2f78d8c2d299c04500cb0d/compiled.gif)
![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif)
- Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests
......@@ -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)
- [![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)
......@@ -29,14 +29,14 @@
## Website
On [www.gitlab.com](https://www.gitlab.com/) you can find more information about:
On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
- [Subscriptions](https://www.gitlab.com/subscription/)
- [Consultancy](https://www.gitlab.com/consultancy/)
- [Community](https://www.gitlab.com/community/)
- [Hosted GitLab.com](https://www.gitlab.com/gitlab-com/) use GitLab as a free service
- [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab CI](https://www.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
- [Subscriptions](https://about.gitlab.com/subscription/)
- [Consultancy](https://about.gitlab.com/consultancy/)
- [Community](https://about.gitlab.com/community/)
- [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service
- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Third-party applications
......@@ -61,11 +61,11 @@ These applications are maintained by contributors, GitLab B.V. does not offer su
## Installation
Please see [the installation page on the GitLab website](https://www.gitlab.com/installation/).
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
### 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
......@@ -85,7 +85,8 @@ Please login with `root` / `5iveL!fe`
## 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
......@@ -126,7 +127,7 @@ All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/
## Getting help
Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help.
Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on our website for the many options to get help.
## Is it any good?
......
7.3.0.pre
7.4.0-pre
......@@ -46,10 +46,10 @@ class Admin
modal.hide()
$('.change-owner-link').show()
$('li.users_project').bind 'ajax:success', ->
$('li.project_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
$('li.users_group').bind 'ajax:success', ->
$('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
@Admin = Admin
......@@ -15,22 +15,31 @@
#= require jquery.atwho
#= require jquery.scrollTo
#= require jquery.blockUI
#= require turbolinks
#= require jquery.turbolinks
#= require turbolinks
#= require bootstrap
#= require select2
#= require raphael
#= require g.raphael-min
#= require g.bar-min
#= require chart-lib.min
#= require branch-graph
#= require highlight.pack
#= require ace/ace
#= require ace/ext-searchbox
#= require d3
#= require underscore
#= require nprogress
#= require nprogress-turbolinks
#= require dropzone
#= require semantic-ui/sidebar
#= require mousetrap
#= require mousetrap/pause
#= require shortcuts
#= require shortcuts_navigation
#= require shortcuts_dashboard_navigation
#= require shortcuts_issueable
#= require shortcuts_network
#= require_tree .
window.slugify = (text) ->
......@@ -117,6 +126,13 @@ $ ->
# Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
# Close select2 on escape
$('.js-select2').bind 'select2-close', ->
setTimeout ( ->
$('.select2-container-active').removeClass('select2-container-active')
$(':focus').blur()
), 1
# Initialize tooltips
$('.has_tooltip').tooltip()
......@@ -134,7 +150,6 @@ $ ->
if (flash = $(".flash-container")).length > 0
flash.click -> $(@).fadeOut()
flash.show()
setTimeout (-> flash.fadeOut()), 5000
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
......@@ -149,20 +164,6 @@ $ ->
# Show/Hide the profile menu when hovering the account box
$('.account-box').hover -> $(@).toggleClass('hover')
# Focus search field by pressing 's' key
$(document).keypress (e) ->
# Don't do anything if typing in an input
return if $(e.target).is(":input")
switch e.which
when 115
$("#search").focus()
e.preventDefault()
when 63
new Shortcuts()
e.preventDefault()
# Commit show suppressed diff
$(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show()
......
$ ->
# 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) ->
@preparedCommits = {}
@mtime = 0
......@@ -90,11 +90,15 @@ class BranchGraph
renderPartialGraph: ->
start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
start = 0 if start < 0
if start < 0
isGraphEdge = true
start = 0
end = start + 40
end = @commits.length if @commits.length < end
if @commits.length < end
isGraphEdge = true
end = @commits.length
if @prev_start == -1 or Math.abs(@prev_start - start) > 10
if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge
i = start
@prev_start = start
......@@ -120,23 +124,32 @@ class BranchGraph
@top.toFront()
bindEvents: ->
drag = {}
element = @element
$(element).scroll (event) =>
@renderPartialGraph()
$(window).on
keydown: (event) =>
# left
element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37
# top
element.scrollTop element.scrollTop() - 50 if event.keyCode is 38
# right
element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39
# bottom
element.scrollTop element.scrollTop() + 50 if event.keyCode is 40
@renderPartialGraph()
scrollDown: =>
@element.scrollTop @element.scrollTop() + 50
@renderPartialGraph()
scrollUp: =>
@element.scrollTop @element.scrollTop() - 50
@renderPartialGraph()
scrollLeft: =>
@element.scrollLeft @element.scrollLeft() - 50
@renderPartialGraph()
scrollRight: =>
@element.scrollLeft @element.scrollLeft() + 50
@renderPartialGraph()
scrollBottom: =>
@element.scrollTop @element.find('svg').height()
scrollTop: =>
@element.scrollTop 0
appendLabel: (x, y, commit) ->
return unless commit.refs
......@@ -325,5 +338,3 @@ Raphael::textWrap = (t, width) ->
b = t.getBBox()
h = Math.abs(b.y2) - Math.abs(b.y) + 1
t.attr y: b.y + h
@BranchGraph = BranchGraph
@Chart =
labels: []
values: []
init: (labels, values, title) ->
r = Raphael('activity-chart')
fin = ->
@flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this)
fout = ->
@flag.animate
opacity: 0, 300, -> @remove()
r.text(160, 10, title).attr font: "13px sans-serif"
r.barchart(
10, 20, 560, 200,
[values],
{colors:["#456"]}
).label(labels, true)
.hover(fin, fout)
......@@ -15,49 +15,87 @@ class Dispatcher
return false
path = page.split(':')
shortcut_handler = null
switch page
when 'projects:issues:index'
Issues.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
shortcut_handler = new ShortcutsIssueable()
new ZenMode()
when 'projects:milestones:show'
new Milestone()
when 'projects:issues:new'
when 'projects:issues:new','projects:issues:edit'
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()
new Diff()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssueable()
new ZenMode()
when "projects:merge_requests:diffs"
new Diff()
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
when 'dashboard:show'
new Dashboard()
new Activities()
when 'projects:commit:show'
new Commit()
new Diff()
shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation()
when 'groups:show', 'projects:show'
new Activities()
when 'projects:new', 'projects:edit'
shortcut_handler = new ShortcutsNavigation()
when 'projects:new'
new Project()
when 'projects:edit'
new Project()
shortcut_handler = new ShortcutsNavigation()
when 'projects:teams:members:index'
new TeamMembers()
when 'groups:members'
new GroupMembers()
when 'projects:tree:show'
new TreeView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:blob:show'
new BlobView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
when 'projects:network:show'
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
shortcut_handler = true
switch path.first()
when 'admin' then new Admin()
when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation()
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: ->
opts = $('.search-autocomplete-opts')
......
......@@ -10,6 +10,5 @@ class Flash
flash.click -> $(@).fadeOut()
flash.show()
setTimeout (-> flash.fadeOut()), 5000
@Flash = Flash
class GroupMembers
constructor: ->
$('li.users_group').bind 'ajax:success', ->
$('li.group_member').bind 'ajax:success', ->
$(this).fadeOut()
@GroupMembers = GroupMembers
......
......@@ -43,25 +43,31 @@
$(".selected_issue").bind "change", Issues.checkChanged
# Make sure we trigger ajax request only after user stop typing
initSearch: ->
form = $("#issue_search_form")
last_terms = ""
@timer = null
$("#issue_search").keyup ->
terms = $(this).val()
unless terms is last_terms
last_terms = terms
if terms.length >= 2 or terms.length is 0
$.ajax
type: "GET"
url: location.href
data: "issue_search=" + terms
complete: ->
$(".loading").hide()
success: (data) ->
$('.issues-holder').html(data.html)
Issues.reload()
dataType: "json"
clearTimeout(@timer);
@timer = setTimeout(Issues.filterResults, 500)
filterResults: =>
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.issues-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '? '+ form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
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: ->
checked_issues = $(".selected_issue:checked")
......
......@@ -27,7 +27,7 @@ $(document).ready ->
dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload
dictDefaultMessage: ""
clickable: true
clickable: false
paramName: "markdown_img"
maxFilesize: 10
uploadMultiple: false
......
class Network
class @Network
constructor: (opts) ->
$("#filter_ref").click ->
$(this).closest('form').submit()
branch_graph = new BranchGraph($(".network-graph"), opts)
@branch_graph = new BranchGraph($(".network-graph"), opts)
vph = $(window).height() - 250
$('.network-graph').css 'height': (vph + 'px')
@Network = Network
......@@ -16,13 +16,19 @@ class Notes
$(document).on "ajax:success", ".js-main-target-form", @addNote
$(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
# Edit note link
$(document).on "click", ".js-note-edit", @showEditForm
$(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)
$(document).on "click", ".js-note-delete", @removeNote
......@@ -78,7 +84,9 @@ class Notes
$(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange"
$(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: ->
clearInterval(Notes.interval)
......@@ -406,30 +414,6 @@ class Notes
form.find(".js-note-text").focus()
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.
......@@ -502,4 +486,33 @@ class Notes
visibilityChange: =>
@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
......@@ -47,7 +47,7 @@ $ ->
$(@).parents('.no-ssh-key-message').hide()
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)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
class Shortcuts
class @Shortcuts
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
$('#modal-shortcuts').modal('show')
else
$.ajax(
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')
......@@ -24,22 +24,7 @@ class window.ContributorsStatGraph
class: 'graph-author-commits-count'
})
commits.text(author.commits + " commits")
additions = $('<span/>', {
class: 'graph-additions'
})
additions.text(author.additions + " ++")
deletions = $('<span/>', {
class: 'graph-deletions'
})
deletions.text(author.deletions + " --")
$('<span/>').append(commits)
.append(" / ")
.append(additions)
.append(" / ")
.append(deletions)
create_author_header: (author) ->
list_item = $('<li/>', {
......
......@@ -90,4 +90,4 @@ window.ContributorsStatGraphUtil =
true
else
false
class @ZenMode
@fullscreen_prefix = 'fullscreen_'
constructor: ->
@active_zen_area = null
@active_checkbox = null
@scroll_position = 0
$(window).scroll =>
if not @active_checkbox
@scroll_position = window.pageYOffset
$('body').on 'change', '.zennable input[type=checkbox]', (e) =>
checkbox = e.currentTarget
if checkbox.checked
# Disable other keyboard shortcuts in ZEN mode
Mousetrap.pause()
@udpateActiveZenArea(checkbox)
else
@exitZenMode()
$(document).on 'keydown', (e) =>
if e.keyCode is $.ui.keyCode.ESCAPE
@exitZenMode()
e.preventDefault()
$(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 = ''
window.scrollTo(window.pageXOffset, @scroll_position)
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 {
}
.highlight_word {
border-bottom: 2px solid #F90;
background: #fafe3d;
}
.thin_area{
......
......@@ -60,7 +60,6 @@
.highlight {
margin-bottom: 9px;
@include border-radius(4px);
> pre {
margin: 0;
......
.flash-container {
display: none;
cursor: pointer;
margin: 0;
text-align: center;
color: #fff;
font-size: 14px;
position: fixed;
bottom: 0;
width: 100%;
opacity: 0.8;
z-index: 100;
.flash-notice {
background: #49C;
padding: 10px;
text-shadow: 0 1px 1px #178;
@extend .alert;
@extend .alert-info;
}
.flash-alert {
background: #C67;
text-shadow: 0 1px 1px #945;
padding: 10px;
@extend .alert;
@extend .alert-danger;
}
}
textarea {
resize: vertical;
}
input[type='search'].search-text-input {
background-image: image-url("icon-search.png");
background-repeat: no-repeat;
......@@ -83,3 +87,140 @@ label {
.form-control {
@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;
}
}
/**
* Styles that apply to both issues and merge requests.
*/
.issue-form, .merge-request-form {
.description {
height: 20em;
}
}
......@@ -8,43 +8,51 @@
*/
.issue-box {
color: #666;
color: #555;
margin:20px 0;
background: #FFF;
border: 1px solid #EEE;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
background: #f9f9f9;
border-top-left-radius: 5px;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
&.issue-box-closed {
border-color: $border_danger;
.state {
background-color: #F3CECE;
border-color: $border_danger;
}
.state-label {
background-color: $bg_danger;
color: #FFF;
border-color: $border_danger;
}
}
&.issue-box-merged {
border-color: $border_primary;
.state {
background-color: #B7CEE7;
border-color: $border_primary;
}
.state-label {
background-color: $bg_primary;
color: #FFF;
border-color: $border_primary;
}
}
&.issue-box-open {
border-color: $border_success;
.state {
border-color: $border_success;
background-color: #D6F1D7;
border-color: $bg_success;
}
.state-label {
background-color: $bg_success;
color: #FFF;
}
}
&.issue-box-expired {
border-color: #cea61b;
.state {
background-color: #EEE9B3;
border-color: #faebcc;
}
.state-label {
background: #cea61b;
color: #FFF;
}
......@@ -55,8 +63,7 @@
}
.state {
border-bottom: 1px solid #DDD;
padding: 10px 15px;
background-color: #f9f9f9;
}
.title {
......@@ -88,6 +95,10 @@
.description {
padding: 0 15px 10px 15px;
code {
white-space: pre-wrap;
}
}
.title, .context, .description {
......@@ -100,12 +111,14 @@
font-size: 14px;
float: left;
font-weight: bold;
padding: 10px 15px;
border-top-left-radius: 5px;
}
.creator {
float: right;
padding: 10px 15px;
a {
color: #FFF;
text-decoration: underline;
}
}
......
......@@ -39,7 +39,7 @@
&:hover {
background: $hover;
border-bottom: 1px solid #ADF;
border-bottom: 1px solid darken($hover, 10%);
}
&: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 {
outline: none;
color: $link_color;
&:hover {
text-decoration: none;
text-decoration: underline;
color: $link_hover_color;
}
......@@ -89,6 +89,8 @@ a:focus {
.wiki {
@include md-typography;
word-wrap: break-word;
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
......
.white {
background-color: #fff;
.line.hll {
background: #FFA;
}
.highlight{
border-left: 1px solid #eee;
}
pre {
background-color: #fff;
color: #333;
......@@ -179,8 +173,16 @@
@include box-shadow(0 5px 15px #000);
}
.wiki, .note-body {
.highlight {
border: 1px solid #DDD;
.file-content {
&.code .white {
.highlight {
border-left: 1px solid #eee;
}
}
&.wiki .white {
.highlight, pre, .hljs {
background: #F9F9F9;
}
}
}
......@@ -16,3 +16,4 @@ body {
.container .content {
margin: 0 0;
}
......@@ -2,13 +2,13 @@
* General Colors
*/
$style_color: #474D57;
$hover: #D9EDF7;
$hover: #FFECDB;
/*
* Link colors
*/
$link_color: #446e9b;
$link_hover_color: #2FA0BB;
$link_hover_color: darken($link-color, 10%);
$btn-border: 1px solid #ccc;
......
......@@ -244,7 +244,6 @@ li.commit {
font-family: inherit;
padding-left: $left;
position: relative;
resize: vertical;
z-index: 2;
}
}
......@@ -60,12 +60,13 @@
}
.project-row, .group-row {
padding: 8px 15px !important;
padding: 0 !important;
font-size: 14px;
line-height: 24px;
a {
display: block;
padding: 8px 15px;
}
.project-name, .group-name {
......@@ -99,14 +100,9 @@
margin-right: 15px;
font-size: 20px;
margin-bottom: 15px;
border: 1px solid #EEE;
padding: 8px 12px;
border-radius: 50px;
background: #f5f5f5;
text-align: center;
i {
color: #BBB;
color: #888;
}
}
......
......@@ -125,8 +125,6 @@
}
.line_content {
display: block;
white-space: pre;
height: 18px;
margin: 0px;
padding: 0px 0.5em;
border: none;
......@@ -341,3 +339,12 @@
margin: 0;
border: none;
}
.diff-file .line_content {
white-space: pre;
}
.diff-wrap-lines .line_content {
white-space: pre-wrap;
}
......@@ -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;
}
......@@ -35,39 +35,31 @@
width: 1%;
&.active {
a {
color: #333;
color: $link_color;
font-weight: bold;
&:after {
content: '';
display: block;
position: relative;
bottom: 8px;
left: 50%;
width: 0;
height: 0;
border-color: transparent transparent #333 transparent;
bottom: -1px;
border-color: $link_color;
border-style: solid;
border-width: 6px;
margin-left: -6px;
border-width: 2px;
}
}
}
&:hover {
a {
color: $link_color;
color: $link_hover_color;
&:after {
content: '';
display: block;
position: relative;
bottom: 8px;
left: 50%;
width: 0;
height: 0;
border-color: transparent transparent $link_color transparent;
bottom: -1px;
border-color: $link_hover_color;
border-style: solid;
border-width: 6px;
margin-left: -6px;
border-width: 2px;
}
}
}
......@@ -90,7 +82,6 @@
line-height: 34px;
color: #777;
text-shadow: 0 1px 1px white;
padding: 0 10px;
text-decoration: none;
padding-top: 2px;
}
......
......@@ -17,9 +17,12 @@ ul.notes {
.discussion-header,
.note-header {
@extend .cgray;
padding-top: 5px;
padding-bottom: 15px;
a:hover {
text-decoration: none;
}
.avatar {
float: left;
margin-right: 10px;
......@@ -43,34 +46,19 @@ ul.notes {
}
.discussion {
padding: 10px 0;
overflow: hidden;
display: block;
position:relative;
border-bottom: 1px solid #EEE;
.discussion-body {
margin-left: 50px;
}
}
.note {
padding: 8px 0;
overflow: hidden;
display: block;
position:relative;
border-bottom: 1px solid #eee;
p { color: $style_color; }
.avatar {
margin-top: 3px;
}
.attachment {
font-size: 14px;
}
.note-body {
@include md-typography;
margin-left: 43px;
}
.note-header {
padding-bottom: 3px;
......@@ -80,11 +68,6 @@ ul.notes {
border-bottom: none;
}
}
.note:target {
-webkit-animation:target-note 2s linear;
background: #fffff0;
}
}
.diff-file .notes_holder {
......@@ -99,7 +82,7 @@ ul.notes {
&.notes_line {
text-align: center;
padding: 10px 0;
background: #eee;
background: #FFF;
}
&.notes_line2 {
text-align: center;
......@@ -111,6 +94,9 @@ ul.notes {
border-width: 1px 0;
padding-top: 0;
vertical-align: top;
&.parallel{
border-width: 1px;
}
}
}
}
......@@ -161,8 +147,14 @@ ul.notes {
*/
.diff-file tr.line_holder {
@mixin show-add-diff-note {
filter: alpha(opacity=100);
opacity: 1.0;
}
.add-diff-note {
background: image-url("diff_note_add.png") no-repeat left 0;
border: none;
height: 22px;
margin-left: -65px;
position: absolute;
......@@ -174,8 +166,7 @@ ul.notes {
filter: alpha(opacity=0);
&:hover {
opacity: 1.0;
filter: alpha(opacity=100);
@include show-add-diff-note;
}
}
......@@ -184,8 +175,7 @@ ul.notes {
background: $hover !important;
.add-diff-note {
opacity: 1.0;
filter: alpha(opacity=100);
@include show-add-diff-note;
}
}
}
......@@ -362,3 +352,7 @@ ul.notes {
border-top: 1px solid #DDD;
}
}
.discussion-notes-count {
font-size: 16px;
}
......@@ -15,62 +15,68 @@
}
.project-home-panel {
border-bottom: 1px solid #DDD;
padding-bottom: 15px;
margin-bottom: 30px;
margin-bottom: 15px;
&.empty-project {
border-bottom: 0px;
padding-bottom: 15px;
margin-bottom: 0px;
border-bottom: 0px;
padding-bottom: 15px;
margin-bottom: 0px;
}
.project-home-title {
font-size: 18px;
color: #444;
margin: 0;
line-height: 32px;
}
.project-home-dropdown {
margin-left: 10px;
float: right;
}
.project-home-extra {
margin-top: 15px;
.project-home-row {
@extend .clearfix;
margin-bottom: 15px;
.project-home-desc {
float: left;
color: #777;
margin-bottom: 10px;
color: #666;
font-size: 16px;
}
.project-home-links {
.star-fork-buttons {
float: right;
a {
margin-left: 10px;
font-weight: 500;
min-width: 200px;
font-size: 14px;
font-weight: bold;
.star-buttons, .fork-buttons {
float: right;
margin-left: 20px;
a:hover {
text-decoration: none;
}
.count {
margin-left: 5px;
}
}
}
}
.visibility-level-label {
font-size: 17px;
background: #f1f1f1;
border-radius: 4px;
color: #444;
position: absolute;
margin-left: -55px;
text-shadow: 0 1px 1px #FFF;
width: 40px;
text-align: center;
padding: 6px;
color: #555;
font-weight: bold;
i {
color: inherit;
}
}
}
.project-home-links {
padding: 10px 0px;
float: right;
a {
margin-left: 10px;
font-weight: 500;
}
}
.git-clone-holder {
.project-home-dropdown + & {
margin-right: 45px;
......@@ -159,6 +165,7 @@ ul.nav.nav-projects-tabs {
li {
.project-info {
margin-bottom: 10px;
overflow: hidden;
}
.access-icon {
......@@ -195,8 +202,8 @@ ul.nav.nav-projects-tabs {
white-space: normal;
text-align: left;
padding: 10px 15px;
background-color: #F1f1f1;
border-color: #EEE;
background-color: #F9F9F9;
border-color: #DDD;
&:hover {
background-color: #eee;
......
.search-results {
.search-result-row {
border-bottom: 1px solid #EEE;
padding-bottom: 10px;
margin-bottom: 10px;
}
}
......@@ -8,7 +8,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def show
@members = @group.members.order("group_access DESC").page(params[:members_page]).per(30)
@members = @group.members.order("access_level DESC").page(params[:members_page]).per(30)
@projects = @group.projects.page(params[:projects_page]).per(30)
end
......@@ -40,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def project_teams_update
@group.add_users(params[:user_ids].split(','), params[:group_access])
@group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
......
......@@ -16,10 +16,10 @@ class Admin::ProjectsController < Admin::ApplicationController
def show
if @group
@group_members = @group.members.order("group_access DESC").page(params[:group_members_page]).per(30)
@group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(30)
end
@project_members = @project.users_projects.page(params[:project_members_page]).per(30)
@project_members = @project.project_members.page(params[:project_members_page]).per(30)
end
def transfer
......
class UsersGroupsController < ApplicationController
class Groups::GroupMembersController < ApplicationController
before_filter :group
# Authorize
......@@ -7,18 +7,18 @@ class UsersGroupsController < ApplicationController
layout 'group'
def create
@group.add_users(params[:user_ids].split(','), params[:group_access])
@group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to members_group_path(@group), notice: 'Users were successfully added.'
end
def update
@member = @group.users_groups.find(params[:id])
@member = @group.group_members.find(params[:id])
@member.update_attributes(member_params)
end
def destroy
@users_group = @group.users_groups.find(params[:id])
@users_group = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy
respond_to do |format|
......@@ -43,6 +43,6 @@ class UsersGroupsController < ApplicationController
end
def member_params
params.require(:users_group).permit(:group_access, :user_id)
params.require(:group_member).permit(:access_level, :user_id)
end
end
......@@ -65,15 +65,15 @@ class GroupsController < ApplicationController
def members
@project = group.projects.find(params[:project_id]) if params[:project_id]
@members = group.users_groups
@members = group.group_members
if params[:search].present?
users = group.users.search(params[:search]).to_a
@members = @members.where(user_id: users)
end
@members = @members.order('group_access DESC').page(params[:page]).per(50)
@users_group = UsersGroup.new
@members = @members.order('access_level DESC').page(params[:page]).per(50)
@users_group = GroupMember.new
end
def edit
......
......@@ -2,11 +2,11 @@ class Profiles::GroupsController < ApplicationController
layout "profile"
def index
@user_groups = current_user.users_groups.page(params[:page]).per(20)
@user_groups = current_user.group_members.page(params[:page]).per(20)
end
def leave
@users_group = group.users_groups.where(user_id: current_user.id).first
@users_group = group.group_members.where(user_id: current_user.id).first
if can?(current_user, :destroy, @users_group)
@users_group.destroy
redirect_to(profile_groups_path, info: "You left #{group.name} group.")
......
......@@ -3,8 +3,8 @@ class Profiles::NotificationsController < ApplicationController
def show
@notification = current_user.notification
@users_projects = current_user.users_projects
@users_groups = current_user.users_groups
@project_members = current_user.project_members
@group_members = current_user.group_members
end
def update
......@@ -14,13 +14,13 @@ class Profiles::NotificationsController < ApplicationController
current_user.notification_level = params[:notification_level]
current_user.save
elsif type == 'group'
users_group = current_user.users_groups.find(params[:notification_id])
users_group = current_user.group_members.find(params[:notification_id])
users_group.notification_level = params[:notification_level]
users_group.save
else
users_project = current_user.users_projects.find(params[:notification_id])
users_project.notification_level = params[:notification_level]
users_project.save
project_member = current_user.project_members.find(params[:notification_id])
project_member.notification_level = params[:notification_level]
project_member.save
end
end
end
......@@ -17,13 +17,19 @@ class Projects::BranchesController < Projects::ApplicationController
end
def create
@branch = CreateBranchService.new.execute(project, params[:branch_name], params[:ref], current_user)
redirect_to project_tree_path(@project, @branch.name)
result = CreateBranchService.new(project, current_user).
execute(params[:branch_name], params[:ref])
if result[:status] == :success
@branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name)
else
@error = result[:message]
render action: 'new'
end
end
def destroy
DeleteBranchService.new.execute(project, params[:id], current_user)
DeleteBranchService.new(project, current_user).execute(params[:id])
@branch_name = params[:id]
respond_to do |format|
......
......@@ -19,13 +19,7 @@ class Projects::CommitController < Projects::ApplicationController
[]
end
begin
@diffs = @commit.diffs
rescue Grit::Git::GitTimeout
@diffs = []
@diff_timeout = true
end
@diffs = @commit.diffs
@note = project.build_commit_note(commit)
@notes_count = project.notes.for_commit_id(commit.id).count
@notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
......
......@@ -10,7 +10,8 @@ class Projects::EditTreeController < Projects::BaseTreeController
end
def update
result = Files::UpdateService.new(@project, current_user, params, @ref, @path).execute
result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
......@@ -31,7 +32,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
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
end
......
......@@ -7,19 +7,34 @@ class Projects::GraphsController < Projects::ApplicationController
def show
respond_to do |format|
format.html
format.js do
format.json do
fetch_graph
end
end
end
def commits
@commits = @project.repository.commits(nil, nil, 2000, 0, true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
@commits_per_month = @commits_graph.commits_per_month
end
private
def fetch_graph
@log = @project.repository.graph_log.to_json
@success = true
rescue => ex
@commits = @project.repository.commits(nil, nil, 6000, 0, true)
@log = []
@success = false
@commits.each do |commit|
@log << {
author_name: commit.author_name.force_encoding('UTF-8'),
author_email: commit.author_email.force_encoding('UTF-8'),
date: commit.committed_date.strftime("%Y-%m-%d")
}
end
render json: @log.to_json
end
end
......@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
terms = params['issue_search']
@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)
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
......@@ -28,7 +28,7 @@ class Projects::IssuesController < Projects::ApplicationController
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
sort_param = params[:sort] || 'newest'
@sort = sort_param.humanize unless sort_param.empty?
@assignees = User.where(id: @project.issues.pluck(:assignee_id))
@assignees = User.where(id: @project.issues.pluck(:assignee_id)).active
respond_to do |format|
format.html
......
......@@ -52,7 +52,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format|
format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' }
format.js { render nothing: true }
format.js
end
end
......
......@@ -13,7 +13,7 @@ class Projects::NewTreeController < Projects::BaseTreeController
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path))
else
flash[:alert] = result[:error]
flash[:alert] = result[:message]
render :show
end
end
......
......@@ -30,8 +30,10 @@ class Projects::NotesController < Projects::ApplicationController
end
def update
note.update_attributes(note_params)
note.reset_events_cache
if note.editable?
note.update_attributes(note_params)
note.reset_events_cache
end
respond_to do |format|
format.json { render_note_json(note) }
......@@ -40,8 +42,10 @@ class Projects::NotesController < Projects::ApplicationController
end
def destroy
note.destroy
note.reset_events_cache
if note.editable?
note.destroy
note.reset_events_cache
end
respond_to do |format|
format.js { render nothing: true }
......
......@@ -29,12 +29,10 @@ class Projects::RawController < Projects::ApplicationController
private
def get_blob_type
if @blob.mime_type =~ /html|javascript/
if @blob.text?
'text/plain; charset=utf-8'
elsif @blob.name =~ /(?:msi|exe|rar|r0\d|7z|7zip|zip)$/
'application/octet-stream'
else
@blob.mime_type
'application/octet-stream'
end
end
end
......
......@@ -4,11 +4,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
before_filter :authorize_code_access!
before_filter :require_non_empty_project
def stats
@stats = Gitlab::Git::Stats.new(@repository.raw, @repository.root_ref)
@graph = @stats.graph
end
def archive
unless can?(current_user, :download_code, @project)
render_404 and return
......
......@@ -13,10 +13,15 @@ class Projects::TagsController < Projects::ApplicationController
end
def create
@tag = CreateTagService.new.execute(@project, params[:tag_name],
params[:ref], current_user)
redirect_to project_tags_path(@project)
result = CreateTagService.new(@project, current_user).
execute(params[:tag_name], params[:ref], params[:message])
if result[:status] == :success
@tag = result[:tag]
redirect_to project_tags_path(@project)
else
@error = result[:message]
render action: 'new'
end
end
def destroy
......@@ -28,7 +33,7 @@ class Projects::TagsController < Projects::ApplicationController
respond_to do |format|
format.html { redirect_to project_tags_path }
format.js { render nothing: true }
format.js
end
end
end
......@@ -6,17 +6,17 @@ class Projects::TeamMembersController < Projects::ApplicationController
def index
@group = @project.group
@users_projects = @project.users_projects.order('project_access DESC')
@project_members = @project.project_members.order('access_level DESC')
end
def new
@user_project_relation = project.users_projects.new
@user_project_relation = project.project_members.new
end
def create
users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:project_access]]
@project.team << [users, params[:access_level]]
if params[:redirect_to]
redirect_to params[:redirect_to]
......@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def update
@user_project_relation = project.users_projects.find_by(user_id: member)
@user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params)
unless @user_project_relation.valid?
......@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def destroy
@user_project_relation = project.users_projects.find_by(user_id: member)
@user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation.destroy
respond_to do |format|
......@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def leave
project.users_projects.find_by(user_id: current_user).destroy
project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
format.html { redirect_to :back }
......@@ -69,6 +69,6 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def member_params
params.require(:team_member).permit(:user_id, :project_access)
params.require(:project_member).permit(:user_id, :access_level)
end
end
......@@ -103,7 +103,15 @@ class ProjectsController < ApplicationController
::Projects::DestroyService.new(@project, current_user, {}).execute
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
......
......@@ -4,14 +4,33 @@ class SearchController < ApplicationController
def show
@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?
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
if @project
return access_denied! unless can?(current_user, :download_code, @project)
@search_results = if @project
return access_denied! unless can?(current_user, :download_code, @project)
@search_results = Search::ProjectService.new(@project, current_user, params).execute
else
@search_results = Search::GlobalService.new(current_user, params).execute
end
unless %w(blobs notes issues merge_requests wiki_blobs).
include?(@scope)
@scope = 'blobs'
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
def autocomplete
......
# BaseFinder
# IssuableFinder
#
# Used to filter Issues and MergeRequests collections by set of params
#
......@@ -16,7 +16,9 @@
# label_name: string
# sort: string
#
class BaseFinder
require_relative 'projects_finder'
class IssuableFinder
attr_accessor :current_user, :params
def execute(current_user, params)
......
......@@ -15,7 +15,7 @@
# label_name: string
# sort: string
#
class IssuesFinder < BaseFinder
class IssuesFinder < IssuableFinder
def klass
Issue
end
......
......@@ -15,7 +15,7 @@
# label_name: string
# sort: string
#
class MergeRequestsFinder < BaseFinder
class MergeRequestsFinder < IssuableFinder
def klass
MergeRequest
end
......
......@@ -19,10 +19,8 @@ class ProjectsFinder
# Return ALL group projects
group.projects
else
projects_members = UsersProject.where(
project_id: group.projects,
user_id: current_user
)
projects_members = ProjectMember.in_projects(group.projects).
with_user(current_user)
if projects_members.any?
# User is a project member
......@@ -34,7 +32,7 @@ class ProjectsFinder
#
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_members.pluck(:project_id),
projects_members.pluck(:source_id),
Project.public_and_internal_levels
)
else
......
......@@ -178,6 +178,8 @@ module ApplicationHelper
def search_placeholder
if @project && @project.persisted?
"Search in this project"
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
"Search in this group"
else
......@@ -185,13 +187,6 @@ module ApplicationHelper
end
end
def first_line(str)
lines = str.split("\n")
line = lines.first
line += "..." if lines.size > 1
line
end
def broadcast_message
BroadcastMessage.current
end
......
......@@ -16,38 +16,6 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer))
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)
if diff.deleted_file
"deleted"
......@@ -63,14 +31,6 @@ module CommitsHelper
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
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
def commits_breadcrumbs
return unless @project && @ref
......@@ -105,82 +65,6 @@ module CommitsHelper
branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
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)
if current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path)
......@@ -229,14 +113,6 @@ module CommitsHelper
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)
link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file' do
......
module DiffHelper
def safe_diff_files(diffs)
def allowed_diff_size
if diff_hard_limit_enabled?
diffs.first(Commit::DIFF_HARD_LIMIT_FILES)
Commit::DIFF_HARD_LIMIT_FILES
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
def show_diff_size_warninig?(diffs)
safe_diff_files(diffs).size < diffs.size
def show_diff_size_warning?(diffs)
diffs.size > allowed_diff_size
end
def diff_hard_limit_enabled?
......@@ -19,4 +25,96 @@ module DiffHelper
false
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
......@@ -52,6 +52,8 @@ module EventsHelper
"#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
elsif event.membership_changed?
"#{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?
"#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}"
else
......@@ -64,6 +66,8 @@ module EventsHelper
project_issue_url(event.project, event.issue)
elsif 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?
if event.note_target
if event.note_commit?
......@@ -94,6 +98,8 @@ module EventsHelper
render "events/event_push", event: event
elsif 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?
render "events/event_note", note: event.note
end
......@@ -130,7 +136,7 @@ module EventsHelper
end
def event_note(text)
text = first_line(text)
text = first_line_in_markdown(text)
text = truncate(text, length: 150)
sanitize(markdown(text), tags: %w(a img b pre p))
end
......
......@@ -51,6 +51,14 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe
end
def first_line_in_markdown(text)
line = text.split("\n").detect do |i|
i.present? && markdown(i).present?
end
line += '...' unless line.nil?
line
end
def render_wiki_content(wiki_page)
if wiki_page.format == :markdown
markdown(wiki_page.content)
......@@ -65,7 +73,12 @@ module GitlabMarkdownHelper
paths.uniq.each do |file_path|
# If project does not have repository
# its nothing to rebuild
if @repository.exists? && !@repository.empty?
#
# TODO: pass project variable to markdown helper instead of using
# instance variable. Right now it generates invalid path for pages out
# of project scope. Example: search results where can be rendered markdown
# from different projects
if @repository && @repository.exists? && !@repository.empty?
new_path = rebuild_path(file_path)
# Finds quoted path so we don't replace other mentions of the string
# eg. "doc/api" will be replaced and "/home/doc/api/text" won't
......
......@@ -52,8 +52,8 @@ module NotesHelper
discussion_id: discussion_id
}
link_to "", "javascript:;", class: "add-diff-note js-add-diff-note-button",
data: data, title: "Add a comment to this line"
button_tag '', class: 'btn add-diff-note js-add-diff-note-button',
data: data, title: 'Add a comment to this line'
end
def link_to_reply_diff(note)
......@@ -67,11 +67,10 @@ module NotesHelper
discussion_id: note.discussion_id
}
link_to "javascript:;", class: "btn reply-btn js-discussion-reply-button",
data: data, title: "Add a reply" do
link_text = ""
link_text < content_tag(:i, nil, class: 'icon-comment')
link_text << "Reply"
end
button_tag class: 'btn reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
link_text = content_tag(:i, nil, class: 'icon-comment')
link_text << ' Reply'
end
end
end
......@@ -123,7 +123,7 @@ module ProjectsHelper
end
def link_to_toggle_star(title, starred, signed_in)
cls = 'btn btn-block'
cls = 'star-btn'
cls += ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do
......@@ -151,11 +151,19 @@ module ProjectsHelper
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
link_to toggle_star_project_path(@project), link_opts do
toggle_html + count_html
toggle_html + ' ' + count_html
end
end
end
def link_to_toggle_fork
out = content_tag(:i, '', class: 'icon-code-fork')
out << ' Fork'
out << content_tag(:span, class: 'count') do
@project.forks_count.to_s
end
end
private
def get_project_nav_tabs(project, current_user)
......@@ -261,4 +269,10 @@ module ProjectsHelper
project_blob_path(project, tree_join(project.default_branch, project.repository.contribution_guide.name))
end
end
def hidden_pass_url(original_url)
result = URI(original_url)
result.password = '*****' if result.password.present?
result
end
end
......@@ -80,7 +80,8 @@ module SearchHelper
# Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5)
ProjectsFinder.new.execute(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
ProjectsFinder.new.execute(current_user).search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
url: project_path(p)
......@@ -91,4 +92,21 @@ module SearchHelper
def search_result_sanitize(str)
Sanitize.clean(str)
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
module Emails
module Groups
def group_access_granted_email(user_group_id)
@membership = UsersGroup.find(user_group_id)
@membership = GroupMember.find(user_group_id)
@group = @membership.group
@target_url = group_url(@group)
mail(to: @membership.user.email,
......
module Emails
module Projects
def project_access_granted_email(user_project_id)
@users_project = UsersProject.find user_project_id
@project = @users_project.project
@project_member = ProjectMember.find user_project_id
@project = @project_member.project
@target_url = project_url(@project)
mail(to: @users_project.user.email,
mail(to: @project_member.user.email,
subject: subject("Access to project was granted"))
end
......
......@@ -14,7 +14,7 @@ class Ability
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject)
when "UsersGroup" then users_group_abilities(user, subject)
when "GroupMember" then users_group_abilities(user, subject)
else []
end.concat(global_abilities(user))
end
......
......@@ -108,4 +108,8 @@ class Commit
super
end
def parents
@parents ||= Commit.decorate(super)
end
end
......@@ -49,6 +49,10 @@ module Issuable
where("LOWER(title) like :query", query: "%#{query.downcase}%")
end
def full_search(query)
where("LOWER(title) like :query OR LOWER(description) like :query", query: "%#{query.downcase}%")
end
def sort(method)
case method.to_s
when 'newest' then reorder("#{table_name}.created_at DESC")
......
# == Notifiable concern
#
# Contains notification functionality shared between UsersProject and UsersGroup
# Contains notification functionality
#
module Notifiable
extend ActiveSupport::Concern
......
......@@ -17,8 +17,8 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Group < Namespace
has_many :users_groups, dependent: :destroy
has_many :users, through: :users_groups
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :users, through: :group_members
validate :avatar_type, if: ->(user) { user.avatar_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
......@@ -30,22 +30,22 @@ class Group < Namespace
end
def owners
@owners ||= users_groups.owners.map(&:user)
@owners ||= group_members.owners.map(&:user)
end
def add_users(user_ids, group_access)
def add_users(user_ids, access_level)
user_ids.compact.each do |user_id|
user = self.users_groups.find_or_initialize_by(user_id: user_id)
user.update_attributes(group_access: group_access)
user = self.group_members.find_or_initialize_by(user_id: user_id)
user.update_attributes(access_level: access_level)
end
end
def add_user(user, group_access)
self.users_groups.create(user_id: user.id, group_access: group_access)
def add_user(user, access_level)
self.group_members.create(user_id: user.id, access_level: access_level)
end
def add_owner(user)
self.add_user(user, UsersGroup::OWNER)
self.add_user(user, Gitlab::Access::OWNER)
end
def has_owner?(user)
......@@ -61,7 +61,7 @@ class Group < Namespace
end
def members
users_groups
group_members
end
def avatar_type
......
......@@ -23,7 +23,7 @@ class WebHook < ActiveRecord::Base
default_value_for :merge_requests_events, false
# HTTParty timeout
default_timeout 10
default_timeout Gitlab.config.gitlab.webhook_timeout
validates :url, presence: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
......
......@@ -29,7 +29,9 @@ class Key < ActiveRecord::Base
after_create :add_to_shell
after_create :notify_user
after_create :post_create_hook
after_destroy :remove_from_shell
after_destroy :post_destroy_hook
def strip_white_space
self.key = key.strip unless key.blank?
......@@ -56,6 +58,10 @@ class Key < ActiveRecord::Base
NotificationService.new.new_key(self)
end
def post_create_hook
SystemHooksService.new.execute_hooks_for(self, :create)
end
def remove_from_shell
GitlabShellWorker.perform_async(
:remove_key,
......@@ -64,6 +70,10 @@ class Key < ActiveRecord::Base
)
end
def post_destroy_hook
SystemHooksService.new.execute_hooks_for(self, :destroy)
end
private
def generate_fingerpint
......
class Member < ActiveRecord::Base
include Notifiable
include Gitlab::Access
belongs_to :user
belongs_to :source, polymorphic: true
validates :user, presence: true
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
scope :developers, -> { where(access_level: DEVELOPER) }
scope :masters, -> { where(access_level: MASTER) }
scope :owners, -> { where(access_level: OWNER) }
delegate :name, :username, :email, to: :user, prefix: true
end
class GroupMember < Member
SOURCE_TYPE = 'Namespace'
belongs_to :group, class_name: 'Group', foreign_key: 'source_id'
# Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE
default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\ANamespace\z/
default_scope { where(source_type: SOURCE_TYPE) }
scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
after_create :notify_create
after_update :notify_update
def self.access_level_roles
Gitlab::Access.options_with_owner
end
def group
source
end
def access_field
access_level
end
def notify_create
notification_service.new_group_member(self)
end
def notify_update
if access_level_changed?
notification_service.update_group_member(self)
end
end
def notification_service
NotificationService.new
end
end
# == Schema Information
#
# Table name: users_projects
#
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# project_access :integer default(0), not null
# notification_level :integer default(3), not null
#
class UsersProject < ActiveRecord::Base
include Gitlab::ShellAdapter
include Notifiable
include Gitlab::Access
belongs_to :user
belongs_to :project
class ProjectMember < Member
SOURCE_TYPE = 'Project'
validates :user, presence: true
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_access, inclusion: { in: Gitlab::Access.values }, presence: true
validates :project, presence: true
include Gitlab::ShellAdapter
delegate :name, :username, :email, to: :user, prefix: true
belongs_to :project, class_name: 'Project', foreign_key: 'source_id'
scope :guests, -> { where(project_access: GUEST) }
scope :reporters, -> { where(project_access: REPORTER) }
scope :developers, -> { where(project_access: DEVELOPER) }
scope :masters, -> { where(project_access: MASTER) }
scope :in_project, ->(project) { where(project_id: project.id) }
scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) }
scope :with_user, ->(user) { where(user_id: user.id) }
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\AProject\z/
default_scope { where(source_type: SOURCE_TYPE) }
after_create :post_create_hook
after_update :post_update_hook
after_destroy :post_destroy_hook
scope :in_project, ->(project) { where(source_id: project.id) }
scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) }
scope :with_user, ->(user) { where(user_id: user.id) }
class << self
# Add users to project teams with passed access option
......@@ -50,7 +31,7 @@ class UsersProject < ActiveRecord::Base
# add_users_into_projects(
# project_ids,
# user_ids,
# UsersProject::MASTER
# ProjectMember::MASTER
# )
#
# add_users_into_projects(
......@@ -60,20 +41,20 @@ class UsersProject < ActiveRecord::Base
# )
#
def add_users_into_projects(project_ids, user_ids, access)
project_access = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
access
else
raise "Non valid access"
end
UsersProject.transaction do
access_level = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
access
else
raise "Non valid access"
end
ProjectMember.transaction do
project_ids.each do |project_id|
user_ids.each do |user_id|
users_project = UsersProject.new(project_access: project_access, user_id: user_id)
users_project.project_id = project_id
users_project.save
member = ProjectMember.new(access_level: access_level, user_id: user_id)
member.source_id = project_id
member.save
end
end
end
......@@ -84,10 +65,10 @@ class UsersProject < ActiveRecord::Base
end
def truncate_teams(project_ids)
UsersProject.transaction do
users_projects = UsersProject.where(project_id: project_ids)
users_projects.each do |users_project|
users_project.destroy
ProjectMember.transaction do
members = ProjectMember.where(source_id: project_ids)
members.each do |member|
member.destroy
end
end
......@@ -110,7 +91,7 @@ class UsersProject < ActiveRecord::Base
end
def access_field
project_access
access_level
end
def owner?
......@@ -129,7 +110,7 @@ class UsersProject < ActiveRecord::Base
end
def post_update_hook
notification_service.update_team_member(self) if self.project_access_changed?
notification_service.update_team_member(self) if self.access_level_changed?
end
def post_destroy_hook
......@@ -149,4 +130,8 @@ class UsersProject < ActiveRecord::Base
def system_hook_service
SystemHooksService.new
end
def project
source
end
end
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.
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