Commit 1f43fa20 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch '6-4-stable' of dev.gitlab.org:gitlab/gitlabhq into 6-4-from-ce

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>

Conflicts:
	Gemfile
	Gemfile.lock
	VERSION
	app/controllers/application_controller.rb
	db/schema.rb
	doc/install/installation.md
	doc/update/6.2-to-6.3.md
parents 20fc9711 0e4a8e23
...@@ -16,9 +16,12 @@ rvm: ...@@ -16,9 +16,12 @@ rvm:
- 2.0.0 - 2.0.0
services: services:
- mysql - mysql
- redis-server
before_script: before_script:
- "cp config/database.yml.$DB config/database.yml" - "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml" - "cp config/gitlab.yml.example config/gitlab.yml"
- "bundle exec rake db:setup" - "bundle exec rake db:setup"
- "bundle exec rake db:seed_fu" - "bundle exec rake db:seed_fu"
script: "bundle exec rake $TASK --trace" script: "bundle exec rake $TASK --trace"
notifications:
email: false
v 6.4.0
- Added sorting to project issues page (Jason Blanchard)
- Assembla integration (Carlos Paramio)
- Fixed another 500 error with submodules
- UI: More compact issues page
- Minimal password length increased to 8 symbols
- Side-by-side diff view (Steven Thonus)
- Internal projects (Jason Hollingsworth)
- Allow removal of avatar (Drew Blessing)
- Project web hooks now support issues and merge request events
- Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth)
- Expire event cache on avatar creation/removal (Drew Blessing)
- Archiving old projects (Steven Thonus)
- Rails 4
- Add time ago tooltips to show actual date/time
- UI: Fixed UI for admin system hooks
- Ruby script for easier GitLab upgrade
- Do not remove Merge requests if fork project was removed
- Improve sign-in/signup UX
- Add resend confirmation link to sign-in page
- Set noreply@HOSTNAME for reply_to field in all emails
- Show GitLab API version on Admin#dashboard
- API Cross-origin resource sharing
- Show READMe link at project home page
- Show repo size for projects in Admin area
v 6.3.0 v 6.3.0
- API for adding gitlab-ci service - API for adding gitlab-ci service
- Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey) - Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey)
...@@ -58,7 +84,7 @@ v 6.2.0 ...@@ -58,7 +84,7 @@ v 6.2.0
- Avatar upload on profile page with a maximum of 100KB (Steven Thonus) - Avatar upload on profile page with a maximum of 100KB (Steven Thonus)
- Store the sessions in Redis instead of the cookie store - Store the sessions in Redis instead of the cookie store
- Fixed relative links in markdown - Fixed relative links in markdown
- User must confirm his email if signup enabled - User must confirm their email if signup enabled
- User must confirm changed email - User must confirm changed email
v 6.1.0 v 6.1.0
...@@ -80,7 +106,7 @@ v 6.1.0 ...@@ -80,7 +106,7 @@ v 6.1.0
- Add links to create branch/tag from project home page - Add links to create branch/tag from project home page
- Add public-project? checkbox to new-project view - Add public-project? checkbox to new-project view
- Improved compare page. Added link to proceed into Merge Request - Improved compare page. Added link to proceed into Merge Request
- Send email to user when he was added to group - Send an email to a user when they are added to group
- New landing page when you have 0 projects - New landing page when you have 0 projects
v 6.0.0 v 6.0.0
......
...@@ -65,8 +65,13 @@ If you can, please submit a pull request with the fix or improvements including ...@@ -65,8 +65,13 @@ If you can, please submit a pull request with the fix or improvements including
1. Add your changes to the [CHANGELOG](CHANGELOG) 1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork 1. Push the commit to your fork
1. Submit a pull request 1. Submit a pull request (PR)
2. [Search for issues](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues) related to your pull request and mention them in the pull request description 1. The PR title should describes the change you want to make
1. The PR description should give a motive for your change and the method you used to achieve it
* If the PR changes the UI it should include before and after screenshots
1. [Search for issues](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues) related to your pull request and mention them in the pull request description
Please keep the change in a single PR 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? The smaller a PR is the more likely it is it will be merged, after that you can send more PR's to enhance it.
We will accept pull requests if: We will accept pull requests if:
...@@ -74,11 +79,9 @@ We will accept pull requests if: ...@@ -74,11 +79,9 @@ We will accept pull requests if:
* It can be merged without problems (if not please use: `git rebase master`) * It can be merged without problems (if not please use: `git rebase master`)
* It does not break any existing functionality * It does not break any existing functionality
* It's quality code that conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices * It's quality code that conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices
* The description includes a motive for your change and the method you used to achieve it
* It is not a catch all pull request but rather fixes a specific issue or implements a specific feature * It is not a catch all pull request but rather fixes a specific issue or implements a specific feature
* It keeps the GitLab code base clean and well structured * It keeps the GitLab code base clean and well structured
* We think other users will benefit from the same functionality * We think other users will benefit from the same functionality
* If it makes changes to the UI the pull request should include screenshots
* It is a single commit (please use `git rebase -i` to squash commits) * It is a single commit (please use `git rebase -i` to squash commits)
For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed). For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed).
...@@ -8,15 +8,21 @@ def linux_only(require_as) ...@@ -8,15 +8,21 @@ def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as RUBY_PLATFORM.include?('linux') && require_as
end end
gem "rails", "3.2.16" gem "rails", "~> 4.0.0"
gem "protected_attributes"
gem 'rails-observers'
gem 'actionpack-page_caching'
gem 'actionpack-action_caching'
gem 'activerecord-deprecated_finders'
# Supported DBs # Supported DBs
gem "mysql2", group: :mysql gem "mysql2", group: :mysql
gem "pg", group: :postgres gem "pg", group: :postgres
# Auth # Auth
gem "devise", '~> 2.2' gem "devise", '3.0.4'
gem "devise-async" gem "devise-async", '0.8.0'
gem 'omniauth', "~> 1.1.3" gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2' gem 'omniauth-google-oauth2'
gem 'omniauth-twitter' gem 'omniauth-twitter'
...@@ -24,27 +30,28 @@ gem 'omniauth-github' ...@@ -24,27 +30,28 @@ gem 'omniauth-github'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", "~> 3.0.0.rc2" gem "gitlab_git", "~> 4.0.0.pre"
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 1.0.1', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth # LDAP Auth
gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap"
gem 'net-ldap' gem 'net-ldap'
# Syntax highlighter # Syntax highlighter
gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb' gem "gitlab-pygments.rb", '~> 0.5.4', require: 'pygments.rb'
# Git Wiki # Git Wiki
gem "gitlab-gollum-lib", "~> 1.0.1", require: 'gollum-lib' gem "gitlab-gollum-lib", "~> 1.0.2", require: 'gollum-lib'
# Language detection # Language detection
gem "github-linguist", require: "linguist" gem "gitlab-linguist", "~> 2.9.6", require: "linguist"
# API # API
gem "grape", "~> 0.4.1" gem "grape", "~> 0.6.1"
gem "grape-entity", "~> 0.3.0" gem "grape-entity", "~> 0.3.0"
gem 'rack-cors', require: 'rack/cors'
# Format dates and times # Format dates and times
# based on human-friendly examples # based on human-friendly examples
...@@ -79,7 +86,10 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup' ...@@ -79,7 +86,10 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup'
gem "asciidoctor" gem "asciidoctor"
# Application server # Application server
gem "unicorn", '~> 4.6.3', group: :unicorn group :unicorn do
gem "unicorn", '~> 4.6.3'
gem 'unicorn-worker-killer'
end
# State machine # State machine
gem "state_machine" gem "state_machine"
...@@ -128,26 +138,24 @@ gem "sanitize" ...@@ -128,26 +138,24 @@ gem "sanitize"
# Protect against bruteforcing # Protect against bruteforcing
gem "rack-attack" gem "rack-attack"
group :assets do gem "sass-rails"
gem "sass-rails" gem "coffee-rails"
gem "coffee-rails" gem "uglifier"
gem "uglifier" gem "therubyracer"
gem "therubyracer" gem 'turbolinks'
gem 'turbolinks' gem 'jquery-turbolinks'
gem 'jquery-turbolinks'
gem 'chosen-rails', "1.0.1"
gem 'chosen-rails', "1.0.0" gem 'select2-rails'
gem 'select2-rails' gem 'jquery-atwho-rails', "~> 0.3.3"
gem 'jquery-atwho-rails', "0.3.0" gem "jquery-rails", "2.1.3"
gem "jquery-rails", "2.1.3" gem "jquery-ui-rails", "2.0.2"
gem "jquery-ui-rails", "2.0.2" gem "modernizr", "2.6.2"
gem "modernizr", "2.6.2" gem "raphael-rails", "~> 2.1.2"
gem "raphael-rails", "~> 2.1.2" gem 'bootstrap-sass', '~> 2.3'
gem 'bootstrap-sass' gem "font-awesome-rails", '~> 3.2'
gem "font-awesome-rails" gem "gemoji", "~> 1.3.0"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' gem "gon", git: "https://github.com/gitlabhq/gon.git", ref: '58ca8e17273051cb370182cabd3602d1da6783ab'
gem "gon"
end
group :development do group :development do
gem "annotate", "~> 2.6.0.beta2" gem "annotate", "~> 2.6.0.beta2"
...@@ -170,7 +178,7 @@ end ...@@ -170,7 +178,7 @@ end
group :development, :test do group :development, :test do
gem 'coveralls', require: false gem 'coveralls', require: false
gem 'rails-dev-tweaks' # gem 'rails-dev-tweaks'
gem 'spinach-rails' gem 'spinach-rails'
gem "rspec-rails" gem "rspec-rails"
gem "capybara" gem "capybara"
...@@ -199,7 +207,7 @@ group :development, :test do ...@@ -199,7 +207,7 @@ group :development, :test do
gem 'poltergeist', '~> 1.4.1' gem 'poltergeist', '~> 1.4.1'
gem 'spork', '~> 1.0rc' gem 'spork', '~> 1.0rc'
gem 'jasmine' gem 'jasmine', '2.0.0.rc5'
end end
group :test do group :test do
......
This diff is collapsed.
...@@ -32,7 +32,9 @@ ...@@ -32,7 +32,9 @@
* GitLab.com commercial services: [Homepage](http://www.gitlab.com/) | [Subscription](http://www.gitlab.com/subscription/) | [Consultancy](http://www.gitlab.com/consultancy/) | [GitLab Cloud](http://www.gitlab.com/cloud/) | [Blog](http://blog.gitlab.com/) * GitLab.com commercial services: [Homepage](http://www.gitlab.com/) | [Subscription](http://www.gitlab.com/subscription/) | [Consultancy](http://www.gitlab.com/consultancy/) | [GitLab Cloud](http://www.gitlab.com/cloud/) | [Blog](http://blog.gitlab.com/)
* GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server * [GitLab Enterprise Edition](https://www.gitlab.com/features/) offers additional features that are useful for larger organizations (100+ users).
* [GitLab CI](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) is a continuous integration (CI) server that is easy to integrate with GitLab.
### Requirements ### Requirements
...@@ -46,30 +48,24 @@ ...@@ -46,30 +48,24 @@
### Installation ### Installation
#### Official production installation #### Official installation methods
* [Installation guide for a production server](doc/install/installation.md)
* [Manual installation guide for a production server](doc/install/installation.md)
#### Official development installation * [GitLab Chef Cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) This cookbook can be used both for development installations and production installations. If you want to [contribute](CONTRIBUTE.md) to GitLab we suggest you follow the [development installation on a virtual machine with Vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) instructions to install all testing dependencies.
If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working with all dependencies. #### Third party one-click installers
* [Vagrant virtual machine for development](https://github.com/gitlabhq/gitlab-vagrant-vm) * [Digital Ocean 1-Click Application Install](https://www.digitalocean.com/blog_posts/host-your-git-repositories-in-55-seconds-with-gitlab) Have a new server up in 55 seconds. Digital Ocean uses SSD disks which is great for an IO intensive app such as GitLab.
* [BitNami one-click installers](http://bitnami.com/stack/gitlab) This package contains both GitLab and GitLab CI. It is available as installer, virtual machine or for cloud hosting providers (Amazon Web Services/Azure/etc.).
#### Unofficial production installations #### Unofficial installation methods
* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version. * [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version.
* [Installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) public wiki with unofficial guides to install GitLab on different operating systems. * [Installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) public wiki with unofficial guides to install GitLab on different operating systems.
* [Digital Ocean 1-Click Application Install](https://www.digitalocean.com/) Have a new server up in 55 seconds. Digital Ocean uses SSD disks which is great for an IO intensive app as GitLab. Look for GitLab under 'Select Image' => 'Applications' when creating a droplet.
* [BitNami one-click installers](http://bitnami.com/stack/gitlab) Get an image with GitLab and GitLab CI preinstalled for Amazon Web Services, Azure, VMware or your local server.
### New versions and upgrading ### New versions and upgrading
Since 2011 GitLab is released on the 22nd of every month. Every new release includes an upgrade guide. Since 2011 GitLab is released on the 22nd of every month. Every new release includes an upgrade guide.
...@@ -80,7 +76,6 @@ Since 2011 GitLab is released on the 22nd of every month. Every new release incl ...@@ -80,7 +76,6 @@ Since 2011 GitLab is released on the 22nd of every month. Every new release incl
* Features that will be in the next releases are listed on [the feedback and suggestions 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). * Features that will be in the next releases are listed on [the feedback and suggestions 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).
### Run in production mode ### Run in production mode
The Installation guide contains instructions on how to download an init script and run it automatically on boot. You can also start the init script manually: The Installation guide contains instructions on how to download an init script and run it automatically on boot. You can also start the init script manually:
...@@ -111,7 +106,7 @@ or start each component separately ...@@ -111,7 +106,7 @@ or start each component separately
* Run all tests * Run all tests
bundle exec rake gitlab:test bundle exec rake gitlab:test RAILS_ENV=test
* [RSpec](http://rspec.info/) unit and functional tests * [RSpec](http://rspec.info/) unit and functional tests
...@@ -148,15 +143,17 @@ or start each component separately ...@@ -148,15 +143,17 @@ or start each component separately
* [Mailing list](https://groups.google.com/forum/#!forum/gitlabhq) and [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) are the best places to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please search for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and has resolved it. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix. * [Mailing list](https://groups.google.com/forum/#!forum/gitlabhq) and [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) are the best places to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please search for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and has resolved it. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix.
* [Unofficial #gitlab IRC on Freenode](http://www.freenode.net/) is another way to get in touch with other GitLab users who may be able to help you.
* [Feedback and suggestions forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab. * [Feedback and suggestions forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab.
* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) describes how to submit pull requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed. * [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) describes how to submit pull requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed.
* [Support subscription](http://www.gitlab.com/subscription/) connects you to the knowledge of GitLab experts that will resolve your issues and answer your questions. * [Support subscription](http://www.gitlab.com/subscription/) connects you to the knowledge of GitLab experts that will resolve your issues and answer your questions.
* [Consultancy](http://www.gitlab.com/consultancy/) allows you hire GitLab experts for installations, upgrades and customizations. * [Consultancy](http://www.gitlab.com/consultancy/) from the GitLab experts for installations, upgrades and customizations.
* [#gitlab IRC channel](http://www.freenode.net/) on Freenode to get in touch with other GitLab users and get help, it's managed by James Newton, Drew Blessing and Sam Gleske
* [Book](http://www.packtpub.com/gitlab-repository-management/book) written by GitLab enthusiast Jonathan M. Hethey is unofficial but it offers a good overview.
### Getting in touch ### Getting in touch
......
app/assets/images/favicon.ico

1.12 KB | W: | H:

app/assets/images/favicon.ico

32.2 KB | W: | H:

app/assets/images/favicon.ico
app/assets/images/favicon.ico
app/assets/images/favicon.ico
app/assets/images/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/logo-black.png

3.01 KB | W: | H:

app/assets/images/logo-black.png

2.95 KB | W: | H:

app/assets/images/logo-black.png
app/assets/images/logo-black.png
app/assets/images/logo-black.png
app/assets/images/logo-black.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/logo-white.png

5.59 KB | W: | H:

app/assets/images/logo-white.png

8.14 KB | W: | H:

app/assets/images/logo-white.png
app/assets/images/logo-white.png
app/assets/images/logo-white.png
app/assets/images/logo-white.png
  • 2-up
  • Swipe
  • Onion skin
class BlobView class BlobView
constructor: -> constructor: ->
# handle multi-line select
handleMultiSelect = (e) ->
[ first_line, last_line ] = parseSelectedLines()
[ line_number ] = parseSelectedLines($(this).attr("id"))
hash = "L#{line_number}"
if e.shiftKey and not isNaN(first_line) and not isNaN(line_number)
if line_number < first_line
last_line = first_line
first_line = line_number
else
last_line = line_number
hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}"
setHash(hash)
e.preventDefault()
# See if there are lines selected # See if there are lines selected
# "#L12" and "#L34-56" supported # "#L12" and "#L34-56" supported
highlightBlobLines = -> highlightBlobLines = (e) ->
if window.location.hash isnt "" [ first_line, last_line ] = parseSelectedLines()
matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/)
unless isNaN first_line
$("#tree-content-holder .highlight .line").removeClass("hll")
$("#LC#{line}").addClass("hll") for line in [first_line..last_line]
$("#L#{first_line}").ScrollTo() unless e?
# parse selected lines from hash
# always return first and last line (initialized to NaN)
parseSelectedLines = (str) ->
first_line = NaN
last_line = NaN
hash = str || window.location.hash
if hash isnt ""
matches = hash.match(/\#?L(\d+)(\-(\d+))?/)
first_line = parseInt(matches?[1]) first_line = parseInt(matches?[1])
last_line = parseInt(matches?[3]) last_line = parseInt(matches?[3])
last_line = first_line if isNaN(last_line)
[ first_line, last_line ]
setHash = (hash) ->
hash = hash.replace(/^\#/, "")
nodes = $("#" + hash)
# if any nodes are using this id, they must be temporarily changed
# also, add a temporary div at the top of the screen to prevent scrolling
if nodes.length > 0
scroll_top = $(document).scrollTop()
nodes.attr("id", "")
tmp = $("<div></div>")
.css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" })
.attr("id", hash)
.appendTo(document.body)
window.location.hash = hash
# restore the nodes
if nodes.length > 0
tmp.remove()
nodes.attr("id", hash)
unless isNaN first_line # initialize multi-line select
last_line = first_line if isNaN(last_line) $("#tree-content-holder .line_numbers a[id^=L]").on("click", handleMultiSelect)
$("#tree-content-holder .highlight .line").removeClass("hll")
$("#LC#{line}").addClass("hll") for line in [first_line..last_line]
$("#L#{first_line}").ScrollTo()
# Highlight the correct lines on load # Highlight the correct lines on load
highlightBlobLines() highlightBlobLines()
# Highlight the correct lines when the hash part of the URL changes # Highlight the correct lines when the hash part of the URL changes
$(window).on 'hashchange', highlightBlobLines $(window).on("hashchange", highlightBlobLines)
@BlobView = BlobView @BlobView = BlobView
...@@ -4,13 +4,13 @@ class CommitsList ...@@ -4,13 +4,13 @@ class CommitsList
limit: 0 limit: 0
offset: 0 offset: 0
@disable = false @disable = false
@showProgress: -> @showProgress: ->
$('.loading').show() $('.loading').show()
@hideProgress: -> @hideProgress: ->
$('.loading').hide() $('.loading').hide()
@init: (ref, limit) -> @init: (ref, limit) ->
$(".day-commits-table li.commit").live 'click', (event) -> $(".day-commits-table li.commit").live 'click', (event) ->
if event.target.nodeName != "A" if event.target.nodeName != "A"
...@@ -21,7 +21,7 @@ class CommitsList ...@@ -21,7 +21,7 @@ class CommitsList
@data.ref = ref @data.ref = ref
@data.limit = limit @data.limit = limit
@data.offset = limit @data.offset = limit
this.initLoadMore() this.initLoadMore()
this.showProgress() this.showProgress()
...@@ -32,7 +32,9 @@ class CommitsList ...@@ -32,7 +32,9 @@ class CommitsList
url: location.href url: location.href
data: @data data: @data
complete: this.hideProgress complete: this.hideProgress
dataType: "script" success: (data) ->
CommitsList.append(data.count, data.html)
dataType: "json"
@append: (count, html) -> @append: (count, html) ->
$("#commits-list").append(html) $("#commits-list").append(html)
...@@ -40,7 +42,7 @@ class CommitsList ...@@ -40,7 +42,7 @@ class CommitsList
@data.offset += count @data.offset += count
else else
@disable = true @disable = true
@initLoadMore: -> @initLoadMore: ->
$(document).unbind('scroll') $(document).unbind('scroll')
$(document).endlessScroll $(document).endlessScroll
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
backgroundColor: '#DDD' backgroundColor: '#DDD'
opacity: .4 opacity: .4
) )
reload: -> reload: ->
Issues.initSelects() Issues.initSelects()
Issues.initChecks() Issues.initChecks()
...@@ -54,7 +54,16 @@ ...@@ -54,7 +54,16 @@
unless terms is last_terms unless terms is last_terms
last_terms = terms last_terms = terms
if terms.length >= 2 or terms.length is 0 if terms.length >= 2 or terms.length is 0
form.submit() $.ajax
type: "GET"
url: location.href
data: "issue_search=" + terms
complete: ->
$(".loading").hide()
success: (data) ->
$('.issues-holder').html(data.html)
Issues.reload()
dataType: "json"
checkChanged: -> checkChanged: ->
checked_issues = $(".selected_issue:checked") checked_issues = $(".selected_issue:checked")
......
window.updatePage = (data) ->
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"})
window.slugify = (text) -> window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
...@@ -56,7 +53,7 @@ window.unbindEvents = -> ...@@ -56,7 +53,7 @@ window.unbindEvents = ->
document.addEventListener("page:fetch", startSpinner) document.addEventListener("page:fetch", startSpinner)
document.addEventListener("page:fetch", unbindEvents) document.addEventListener("page:fetch", unbindEvents)
document.addEventListener("page:receive", stopSpinner) document.addEventListener("page:change", stopSpinner)
$ -> $ ->
# Click a .one_click_select field, select the contents # Click a .one_click_select field, select the contents
......
...@@ -21,7 +21,7 @@ class MergeRequest ...@@ -21,7 +21,7 @@ class MergeRequest
this.initMergeWidget() this.initMergeWidget()
this.$('.show-all-commits').on 'click', => this.$('.show-all-commits').on 'click', =>
this.showAllCommits() this.showAllCommits()
modal = $('#modal_merge_info').modal(show: false) modal = $('#modal_merge_info').modal(show: false)
# Local jQuery finder # Local jQuery finder
...@@ -83,12 +83,12 @@ class MergeRequest ...@@ -83,12 +83,12 @@ class MergeRequest
url: this.$('.nav-tabs .diffs-tab a').attr('href') url: this.$('.nav-tabs .diffs-tab a').attr('href')
beforeSend: => beforeSend: =>
this.$('.status').addClass 'loading' this.$('.status').addClass 'loading'
complete: => complete: =>
@diffs_loaded = true @diffs_loaded = true
this.$('.status').removeClass 'loading' this.$('.status').removeClass 'loading'
success: (data) =>
dataType: 'script' this.$(".diffs").html(data.html)
dataType: 'json'
showAllCommits: -> showAllCommits: ->
this.$('.first-commits').remove() this.$('.first-commits').remove()
......
...@@ -6,7 +6,7 @@ var NoteList = { ...@@ -6,7 +6,7 @@ var NoteList = {
target_type: null, target_type: null,
init: function(tid, tt, path) { init: function(tid, tt, path) {
NoteList.notes_path = path + ".js"; NoteList.notes_path = path + ".json";
NoteList.target_id = tid; NoteList.target_id = tid;
NoteList.target_type = tt; NoteList.target_type = tt;
NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id; NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id;
...@@ -411,7 +411,10 @@ var NoteList = { ...@@ -411,7 +411,10 @@ var NoteList = {
data: NoteList.target_params, data: NoteList.target_params,
complete: function(){ $('.js-notes-busy').removeClass("loading")}, complete: function(){ $('.js-notes-busy').removeClass("loading")},
beforeSend: function() { $('.js-notes-busy').addClass("loading") }, beforeSend: function() { $('.js-notes-busy').addClass("loading") },
dataType: "script" success: function(data) {
NoteList.setContent(data.html);
},
dataType: "json"
}); });
}, },
...@@ -419,7 +422,7 @@ var NoteList = { ...@@ -419,7 +422,7 @@ var NoteList = {
* Called in response to getContent(). * Called in response to getContent().
* Replaces the content of #notes-list with the given html. * Replaces the content of #notes-list with the given html.
*/ */
setContent: function(newNoteIds, html) { setContent: function(html) {
$("#notes-list").html(html); $("#notes-list").html(html);
}, },
......
...@@ -19,8 +19,9 @@ ...@@ -19,8 +19,9 @@
data: "limit=" + @limit + "&offset=" + @offset data: "limit=" + @limit + "&offset=" + @offset
complete: -> complete: ->
$(".loading").hide() $(".loading").hide()
success: (data) ->
dataType: "script" Pager.append(data.count, data.html)
dataType: "json"
append: (count, html) -> append: (count, html) ->
$(".content_list").append html $(".content_list").append html
......
...@@ -5,6 +5,7 @@ html { ...@@ -5,6 +5,7 @@ html {
/** LAYOUT **/ /** LAYOUT **/
body { body {
-webkit-font-smoothing: antialiased;
margin-bottom: 20px; margin-bottom: 20px;
} }
...@@ -354,6 +355,7 @@ table { ...@@ -354,6 +355,7 @@ table {
.navbar-gitlab .navbar-inner .nav > li .btn-sign-in { .navbar-gitlab .navbar-inner .nav > li .btn-sign-in {
@extend .btn-new; @extend .btn-new;
padding: 5px 15px; padding: 5px 15px;
text-shadow: none;
} }
.broadcast-message { .broadcast-message {
...@@ -369,6 +371,10 @@ table { ...@@ -369,6 +371,10 @@ table {
&.input-large { &.input-large {
width: 210px; width: 210px;
} }
&.input-clamp {
max-width: 100%;
}
} }
.user-result { .user-result {
......
...@@ -72,10 +72,11 @@ ...@@ -72,10 +72,11 @@
.ui-box-head { .ui-box-head {
.box-title { .box-title {
font-size: 18px; font-size: 20px;
font-weight: normal; font-weight: 500;
line-height: 28px; line-height: 28px;
margin: 0; margin: 0;
color: #444;
} }
h3 { h3 {
margin: 0; margin: 0;
...@@ -154,7 +155,7 @@ ...@@ -154,7 +155,7 @@
} }
.row_title { .row_title {
font-weight: bold; font-weight: 500;
color: #444; color: #444;
&:hover { &:hover {
color: #444; color: #444;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
.cblue { color: #29A } .cblue { color: #29A }
.cblack { color: #111 } .cblack { color: #111 }
.cdark { color: #444 } .cdark { color: #444 }
.camber { color: #ffc000 }
.cwhite { color: #fff!important } .cwhite { color: #fff!important }
.bgred { background: #F2DEDE!important } .bgred { background: #F2DEDE!important }
...@@ -127,3 +128,8 @@ pre.well-pre { ...@@ -127,3 +128,8 @@ pre.well-pre {
.dropdown-menu > li > a { .dropdown-menu > li > a {
text-shadow: none; text-shadow: none;
} }
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background: #29b;
}
...@@ -13,6 +13,13 @@ form { ...@@ -13,6 +13,13 @@ form {
margin-top: 1px !important; margin-top: 1px !important;
} }
} }
&.list-label {
float: none;
padding: 0 !important;
margin: 0;
text-align: left;
}
} }
} }
......
...@@ -15,18 +15,16 @@ ...@@ -15,18 +15,16 @@
> li > a { > li > a {
border-left: 4px solid #EEE; border-left: 4px solid #EEE;
padding: 12px; padding: 12px;
color: #777;
} }
> .active > a { > .active > a {
border-color: $primary_color; border-color: $primary_color;
border-radius: 0; background: none;
background: #F1F1F1; color: #333;
color: $style_color; font-weight: bolder;
font-weight: bold;
text-shadow: 0 1px 1px #fff;
} }
&.nav-stacked-menu { &.nav-stacked-menu {
background: #FAFAFA;
li > a { li > a {
padding: 16px; padding: 16px;
} }
...@@ -36,6 +34,7 @@ ...@@ -36,6 +34,7 @@
&.nav-pills-small { &.nav-pills-small {
> li > a { > li > a {
padding: 8px 12px; padding: 8px 12px;
font-size: 12px;
} }
} }
} }
......
...@@ -20,6 +20,15 @@ ...@@ -20,6 +20,15 @@
label { width: 110px; } label { width: 110px; }
.controls { margin-left: 130px; } .controls { margin-left: 130px; }
.form-actions { padding-left: 130px; background: #fff } .form-actions { padding-left: 130px; background: #fff }
.visibility-levels {
.controls {
margin-bottom: 9px;
}
i {
color: inherit;
}
}
} }
.broadcast-messages { .broadcast-messages {
......
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
font-size: 12px; font-size: 12px;
} }
} }
.old_line, .new_line { .old_line, .new_line, .diff_line {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
border: none; border: none;
...@@ -92,6 +92,15 @@ ...@@ -92,6 +92,15 @@
text-decoration: underline; text-decoration: underline;
} }
} }
&.new {
background: #CFD;
}
&.old {
background: #FDD;
}
}
.diff_line {
padding: 0;
} }
.line_holder { .line_holder {
&.old .old_line, &.old .old_line,
...@@ -122,6 +131,11 @@ ...@@ -122,6 +131,11 @@
color: #ccc; color: #ccc;
background: #fafafa; background: #fafafa;
} }
&.parallel {
display: table-cell;
overflow: hidden;
width: 50%;
}
} }
} }
.image { .image {
...@@ -468,8 +482,8 @@ li.commit { ...@@ -468,8 +482,8 @@ li.commit {
} }
.commit-row-message { .commit-row-message {
color: #555; color: #333;
font-weight: bolder; font-weight: 500;
&:hover { &:hover {
color: #444; color: #444;
text-decoration: underline; text-decoration: underline;
...@@ -478,13 +492,14 @@ li.commit { ...@@ -478,13 +492,14 @@ li.commit {
} }
.commit-row-info { .commit-row-info {
color: #777;
a { a {
color: #777; color: #777;
} }
.committed_ago { .committed_ago {
float: right; float: right;
@extend .cgray;
} }
} }
......
...@@ -100,3 +100,21 @@ ...@@ -100,3 +100,21 @@
padding: 2px 5px; padding: 2px 5px;
} }
} }
.project-access-icon {
margin-left: 10px;
float: left;
margin-right: 15px;
font-size: 20px;
margin-bottom: 15px;
border: 1px solid #EEE;
padding: 8px 12px;
border-radius: 50px;
background: #f5f5f5;
width: 16px;
text-align: center;
i {
color: #BBB;
}
}
...@@ -46,8 +46,8 @@ header { ...@@ -46,8 +46,8 @@ header {
h1 { h1 {
margin: 0; margin: 0;
background: url('logo-black.png') no-repeat center 1px; background: url('logo-black.png') no-repeat center center;
background-size: 38px; background-size: 32px;
float: left; float: left;
height: 40px; height: 40px;
width: 40px; width: 40px;
...@@ -152,8 +152,8 @@ header { ...@@ -152,8 +152,8 @@ header {
.app_logo { .app_logo {
a { a {
h1 { h1 {
background: url('logo-white.png') no-repeat center 1px; background: url('logo-white.png') no-repeat center center;
background-size: 38px; background-size: 32px;
color: #fff; color: #fff;
text-shadow: 0 1px 1px #444; text-shadow: 0 1px 1px #444;
} }
......
...@@ -77,8 +77,8 @@ input.check_all_issues { ...@@ -77,8 +77,8 @@ input.check_all_issues {
@media (min-width: 800px) { .issues_filters select { width: 160px; } } @media (min-width: 800px) { .issues_filters select { width: 160px; } }
@media (min-width: 1200px) { .issues_filters select { width: 220px; } } @media (min-width: 1200px) { .issues_filters select { width: 220px; } }
@media (min-width: 800px) { .issues_bulk_update select { width: 120px; } } @media (min-width: 800px) { .issues_bulk_update .chosen-container { min-width: 120px; } }
@media (min-width: 1200px) { .issues_bulk_update select { width: 160px; } } @media (min-width: 1200px) { .issues_bulk_update .chosen-container { min-width: 160px; } }
.issues-holder { .issues-holder {
.issues_filters { .issues_filters {
...@@ -103,3 +103,19 @@ input.check_all_issues { ...@@ -103,3 +103,19 @@ input.check_all_issues {
.participants { .participants {
margin-bottom: 10px; margin-bottom: 10px;
} }
.issues_bulk_update {
.chosen-container {
text-shadow: none;
}
}
.issue-search-form {
margin: 0;
height: 24px;
.issue_search {
border: 1px solid #DDD !important;
background-color: #f4f4f4;
}
}
...@@ -46,3 +46,10 @@ body.login-page{ ...@@ -46,3 +46,10 @@ body.login-page{
margin: 2px; margin: 2px;
} }
} }
.devise-errors {
h2 {
font-size: 14px;
color: #a00;
}
}
...@@ -130,6 +130,12 @@ ul.notes { ...@@ -130,6 +130,12 @@ ul.notes {
&.notes_line { &.notes_line {
text-align: center; text-align: center;
padding: 10px 0; padding: 10px 0;
background: #eee;
}
&.notes_line2 {
text-align: center;
padding: 10px 0;
border-left: 1px solid #ddd !important;
} }
&.notes_content { &.notes_content {
background-color: $white; background-color: $white;
...@@ -270,10 +276,9 @@ ul.notes { ...@@ -270,10 +276,9 @@ ul.notes {
// preview/edit buttons // preview/edit buttons
> a { > a {
font-size: 24px;
padding: 4px;
position: absolute; position: absolute;
right: 10px; right: 5px;
bottom: -60px;
} }
.note_preview { .note_preview {
background: #f5f5f5; background: #f5f5f5;
...@@ -306,10 +311,8 @@ ul.notes { ...@@ -306,10 +311,8 @@ ul.notes {
.common-note-form { .common-note-form {
margin: 0; margin: 0;
height: 140px;
background: #F9F9F9; background: #F9F9F9;
padding: 3px; padding: 3px;
padding-bottom: 25px;
border: 1px solid #DDD; border: 1px solid #DDD;
} }
...@@ -320,7 +323,7 @@ ul.notes { ...@@ -320,7 +323,7 @@ ul.notes {
padding: 0 5px; padding: 0 5px;
.note-form-option { .note-form-option {
margin-top: 10px; margin-top: 8px;
margin-left: 30px; margin-left: 30px;
@extend .pull-left; @extend .pull-left;
} }
...@@ -358,3 +361,7 @@ ul.notes { ...@@ -358,3 +361,7 @@ ul.notes {
.js-note-attachment-delete { .js-note-attachment-delete {
display: none; display: none;
} }
.parallel-comment {
padding: 6px;
}
...@@ -42,3 +42,8 @@ ...@@ -42,3 +42,8 @@
margin-right: 12px; margin-right: 12px;
} }
.profile-avatar-form-option {
hr {
margin: 10px 0;
}
}
...@@ -19,6 +19,12 @@ ...@@ -19,6 +19,12 @@
padding-bottom: 25px; padding-bottom: 25px;
margin-bottom: 30px; margin-bottom: 30px;
&.empty-project {
border-bottom: 0px;
padding-bottom: 15px;
margin-bottom: 0px;
}
.project-home-title { .project-home-title {
font-size: 18px; font-size: 18px;
color: #777; color: #777;
...@@ -45,7 +51,7 @@ ...@@ -45,7 +51,7 @@
} }
} }
.public-label { .visibility-level-label {
font-size: 14px; font-size: 14px;
background: #f1f1f1; background: #f1f1f1;
padding: 8px 10px; padding: 8px 10px;
...@@ -53,6 +59,10 @@ ...@@ -53,6 +59,10 @@
margin-left: 10px; margin-left: 10px;
color: #888; color: #888;
text-shadow: 0 1px 1px #FFF; text-shadow: 0 1px 1px #FFF;
i {
color: inherit;
}
} }
} }
...@@ -61,13 +71,24 @@ ...@@ -61,13 +71,24 @@
border: 1px solid #E1E1E1; border: 1px solid #E1E1E1;
@include border-radius(4px); @include border-radius(4px);
input[type="text"],
.btn { .btn {
margin-left: 3px;
border: none; border: none;
background: none; @include border-radius(0px);
border-left: 1px solid #E1E1E1;
box-shadow: none; box-shadow: none;
padding: 6px 10px;
}
.btn {
float: left;
background: none;
color: #29b; color: #29b;
padding: 6px;
&:first-child {
@include border-radius-left(4px);
border-left: 0px;
}
&.active { &.active {
color: #333; color: #333;
...@@ -76,20 +97,46 @@ ...@@ -76,20 +97,46 @@
} }
input[type="text"] { input[type="text"] {
margin-left: 2px; cursor: auto;
border: none;
border-radius: 0;
border-left: 1px solid #E1E1E1;
@extend .monospace; @extend .monospace;
box-shadow: none;
background: #FAFAFA; background: #FAFAFA;
padding: 6px 10px;
} }
} }
.project-public-holder { .project-visibility-level-holder {
.help-inline { .controls {
padding-top: 7px; padding-bottom: 9px;
}
.controls {
input {
float: left;
}
.descr {
display: block;
margin-left: 1.5em;
&.restricted {
color: #888;
}
label {
float: none;
padding: 0;
margin: 0;
text-align: left;
}
}
.info {
display: block;
margin-top: 5px;
}
strong {
display: inline-block;
width: 4em;
}
}
i {
color: inherit;
} }
} }
...@@ -130,7 +177,8 @@ ul.nav.nav-projects-tabs { ...@@ -130,7 +177,8 @@ ul.nav.nav-projects-tabs {
margin: 0px; margin: 0px;
} }
.my-projects { .my-projects,
.public-projects {
li { li {
.project-info { .project-info {
margin-bottom: 10px; margin-bottom: 10px;
......
require_relative "base_context"
module Files module Files
class CreateContext < BaseContext class CreateContext < BaseContext
def execute def execute
...@@ -19,13 +21,13 @@ module Files ...@@ -19,13 +21,13 @@ module Files
file_path = path file_path = path
unless file_name =~ Gitlab::Regex.path_regex unless file_name =~ Gitlab::Regex.path_regex
return error("Your changes could not be commited, because file name contains not allowed characters") return error("Your changes could not be committed, because file name contains not allowed characters")
end end
blob = repository.blob_at(ref, file_path) blob = repository.blob_at(ref, file_path)
if blob if blob
return error("Your changes could not be commited, because file with such name exists") return error("Your changes could not be committed, because file with such name exists")
end end
new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
...@@ -37,7 +39,7 @@ module Files ...@@ -37,7 +39,7 @@ module Files
if created_successfully if created_successfully
success success
else else
error("Your changes could not be commited, because the file has been changed") error("Your changes could not be committed, because the file has been changed")
end end
end end
end end
......
require_relative "base_context"
module Files module Files
class DeleteContext < BaseContext class DeleteContext < BaseContext
def execute def execute
...@@ -31,7 +33,7 @@ module Files ...@@ -31,7 +33,7 @@ module Files
if deleted_successfully if deleted_successfully
success success
else else
error("Your changes could not be commited, because the file has been changed") error("Your changes could not be committed, because the file has been changed")
end end
end end
end end
......
require_relative "base_context"
module Files module Files
class UpdateContext < BaseContext class UpdateContext < BaseContext
def execute def execute
...@@ -30,7 +32,7 @@ module Files ...@@ -30,7 +32,7 @@ module Files
if created_successfully if created_successfully
success success
else else
error("Your changes could not be commited, because the file has been changed") error("Your changes could not be committed, because the file has been changed")
end end
end end
end end
......
...@@ -22,7 +22,7 @@ module Issues ...@@ -22,7 +22,7 @@ module Issues
opts[:milestone_id] = milestone_id if milestone_id.present? opts[:milestone_id] = milestone_id if milestone_id.present?
opts[:assignee_id] = assignee_id if assignee_id.present? opts[:assignee_id] = assignee_id if assignee_id.present?
issues = Issue.where(id: issues_ids).all issues = Issue.where(id: issues_ids)
issues = issues.select { |issue| can?(current_user, :modify_issue, issue) } issues = issues.select { |issue| can?(current_user, :modify_issue, issue) }
issues.each do |issue| issues.each do |issue|
......
...@@ -29,8 +29,26 @@ module Issues ...@@ -29,8 +29,26 @@ module Issues
if params[:milestone_id].present? if params[:milestone_id].present?
@issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) @issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
end end
# Sort by :sort param
@issues = sort(@issues, params[:sort])
@issues @issues
end end
private
def sort(issues, condition)
case condition
when 'newest' then issues.except(:order).order('created_at DESC')
when 'oldest' then issues.except(:order).order('created_at ASC')
when 'recently_updated' then issues.except(:order).order('updated_at DESC')
when 'last_updated' then issues.except(:order).order('updated_at ASC')
when 'milestone_due_soon' then issues.except(:order).joins(:milestone).order("milestones.due_date ASC")
when 'milestone_due_later' then issues.except(:order).joins(:milestone).order("milestones.due_date DESC")
else issues
end
end
end end
end end
...@@ -8,6 +8,11 @@ module Projects ...@@ -8,6 +8,11 @@ module Projects
# get namespace id # get namespace id
namespace_id = params.delete(:namespace_id) namespace_id = params.delete(:namespace_id)
# check that user is allowed to set specified visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
params.delete(:visibility_level)
end
# Load default feature settings # Load default feature settings
default_features = Gitlab.config.gitlab.default_projects_features default_features = Gitlab.config.gitlab.default_projects_features
...@@ -17,7 +22,7 @@ module Projects ...@@ -17,7 +22,7 @@ module Projects
wall_enabled: default_features.wall, wall_enabled: default_features.wall,
snippets_enabled: default_features.snippets, snippets_enabled: default_features.snippets,
merge_requests_enabled: default_features.merge_requests, merge_requests_enabled: default_features.merge_requests,
public: default_features.public visibility_level: default_features.visibility_level
}.stringify_keys }.stringify_keys
@project = Project.new(default_opts.merge(params)) @project = Project.new(default_opts.merge(params))
......
...@@ -2,15 +2,15 @@ module Projects ...@@ -2,15 +2,15 @@ module Projects
class UpdateContext < BaseContext class UpdateContext < BaseContext
def execute(role = :default) def execute(role = :default)
params[:project].delete(:namespace_id) params[:project].delete(:namespace_id)
params[:project].delete(:public) unless can?(current_user, :change_public_mode, project) # check that user is allowed to set specified visibility_level
unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:project][:visibility_level])
params[:project].delete(:visibility_level)
end
new_branch = params[:project].delete(:default_branch) new_branch = params[:project].delete(:default_branch)
if project.repository.exists? && new_branch != project.repository.root_ref if project.repository.exists? && new_branch != project.default_branch
GitlabShellWorker.perform_async( project.change_head(new_branch)
:update_repository_head,
project.path_with_namespace,
new_branch
)
end end
project.update_attributes(params[:project], as: role) project.update_attributes(params[:project], as: role)
......
class SearchContext class SearchContext
attr_accessor :project_ids, :params attr_accessor :project_ids, :current_user, :params
def initialize(project_ids, params) def initialize(project_ids, user, params)
@project_ids, @params = project_ids, params.dup @project_ids, @current_user, @params = project_ids, user, params.dup
end end
def execute def execute
...@@ -10,7 +10,8 @@ class SearchContext ...@@ -10,7 +10,8 @@ class SearchContext
query = Shellwords.shellescape(query) if query.present? query = Shellwords.shellescape(query) if query.present?
return result unless query.present? return result unless query.present?
result[:projects] = Project.where("projects.id in (?) OR projects.public = true", project_ids).search(query).limit(20) visibility_levels = @current_user ? [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] : [ Gitlab::VisibilityLevel::PUBLIC ]
result[:projects] = Project.where("projects.id in (?) OR projects.visibility_level in (?)", project_ids, visibility_levels).search(query).limit(20)
# Search inside single project # Search inside single project
single_project_search(Project.where(id: project_ids), query) single_project_search(Project.where(id: project_ids), query)
......
...@@ -7,8 +7,8 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -7,8 +7,8 @@ class Admin::ProjectsController < Admin::ApplicationController
owner_id = params[:owner_id] owner_id = params[:owner_id]
user = User.find_by_id(owner_id) user = User.find_by_id(owner_id)
@projects = user ? user.owned_projects : Project.scoped @projects = user ? user.owned_projects : Project.all
@projects = @projects.where(public: true) if params[:public_only].present? @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present? @projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.search(params[:name]) if params[:name].present?
......
...@@ -2,8 +2,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -2,8 +2,7 @@ class Admin::UsersController < Admin::ApplicationController
before_filter :user, only: [:show, :edit, :update, :destroy] before_filter :user, only: [:show, :edit, :update, :destroy]
def index def index
@users = User.scoped @users = User.filter(params[:filter])
@users = @users.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present? @users = @users.search(params[:name]) if params[:name].present?
@users = @users.alphabetically.page(params[:page]) @users = @users.alphabetically.page(params[:page])
end end
......
require 'gon'
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
before_filter :authenticate_user! before_filter :authenticate_user!
before_filter :reject_blocked! before_filter :reject_blocked!
...@@ -8,6 +10,7 @@ class ApplicationController < ActionController::Base ...@@ -8,6 +10,7 @@ class ApplicationController < ActionController::Base
before_filter :dev_tools if Rails.env == 'development' before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers before_filter :default_headers
before_filter :add_gon_variables before_filter :add_gon_variables
before_filter :configure_permitted_parameters, if: :devise_controller?
protect_from_forgery protect_from_forgery
...@@ -82,6 +85,9 @@ class ApplicationController < ActionController::Base ...@@ -82,6 +85,9 @@ class ApplicationController < ActionController::Base
if @project and can?(current_user, :read_project, @project) if @project and can?(current_user, :read_project, @project)
@project @project
elsif current_user.nil?
@project = nil
authenticate_user!
else else
@project = nil @project = nil
render_404 and return render_404 and return
...@@ -103,7 +109,7 @@ class ApplicationController < ActionController::Base ...@@ -103,7 +109,7 @@ class ApplicationController < ActionController::Base
end end
def authorize_code_access! def authorize_code_access!
return access_denied! unless can?(current_user, :download_code, project) or project.public? return access_denied! unless can?(current_user, :download_code, project)
end end
def authorize_push! def authorize_push!
...@@ -193,4 +199,31 @@ class ApplicationController < ActionController::Base ...@@ -193,4 +199,31 @@ class ApplicationController < ActionController::Base
def gitlab_ldap_access def gitlab_ldap_access
Gitlab::LDAP::Access.new Gitlab::LDAP::Access.new
end end
# JSON for infinite scroll via Pager object
def pager_json(partial, count)
html = render_to_string(
partial,
layout: false,
formats: [:html]
)
render json: {
html: html,
count: count
}
end
def view_to_html_string(partial)
render_to_string(
partial,
layout: false,
formats: [:html]
)
end
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password) }
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) }
end
end end
...@@ -22,7 +22,7 @@ class DashboardController < ApplicationController ...@@ -22,7 +22,7 @@ class DashboardController < ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.js format.json { pager_json("events/_events", @events.count) }
format.atom { render layout: false } format.atom { render layout: false }
end end
end end
...@@ -40,6 +40,7 @@ class DashboardController < ApplicationController ...@@ -40,6 +40,7 @@ class DashboardController < ApplicationController
end end
@projects = @projects.where(namespace_id: Group.find_by_name(params[:group])) if params[:group].present? @projects = @projects.where(namespace_id: Group.find_by_name(params[:group])) if params[:group].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.includes(:namespace).sorted_by_activity @projects = @projects.includes(:namespace).sorted_by_activity
@labels = current_user.authorized_projects.tags_on(:labels) @labels = current_user.authorized_projects.tags_on(:labels)
...@@ -72,6 +73,6 @@ class DashboardController < ApplicationController ...@@ -72,6 +73,6 @@ class DashboardController < ApplicationController
protected protected
def load_projects def load_projects
@projects = current_user.authorized_projects.sorted_by_activity @projects = current_user.authorized_projects.sorted_by_activity.non_archived
end end
end end
...@@ -40,7 +40,7 @@ class GroupsController < ApplicationController ...@@ -40,7 +40,7 @@ class GroupsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.js format.json { pager_json("events/_events", @events.count) }
format.atom { render layout: false } format.atom { render layout: false }
end end
end end
......
class Profiles::AvatarsController < ApplicationController
layout "profile"
def destroy
@user = current_user
@user.remove_avatar!
@user.save
@user.reset_events_cache
redirect_to profile_path
end
end
...@@ -2,7 +2,7 @@ class Profiles::KeysController < ApplicationController ...@@ -2,7 +2,7 @@ class Profiles::KeysController < ApplicationController
layout "profile" layout "profile"
def index def index
@keys = current_user.keys.order('id DESC').all @keys = current_user.keys.order('id DESC')
end end
def show def show
......
...@@ -13,6 +13,8 @@ class ProfilesController < ApplicationController ...@@ -13,6 +13,8 @@ class ProfilesController < ApplicationController
end end
def update def update
params[:user].delete(:email) if @user.ldap_user?
if @user.update_attributes(params[:user]) if @user.update_attributes(params[:user])
flash[:notice] = "Profile was successfully updated" flash[:notice] = "Profile was successfully updated"
else else
......
...@@ -10,7 +10,7 @@ class Projects::ApplicationController < ApplicationController ...@@ -10,7 +10,7 @@ class Projects::ApplicationController < ApplicationController
id = params[:project_id] || params[:id] id = params[:project_id] || params[:id]
@project = Project.find_with_namespace(id) @project = Project.find_with_namespace(id)
return if @project && @project.public return if @project && @project.public?
end end
super super
......
...@@ -16,7 +16,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -16,7 +16,7 @@ class Projects::BlobController < Projects::ApplicationController
result = Files::DeleteContext.new(@project, current_user, params, @ref, @path).execute result = Files::DeleteContext.new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully commited" flash[:notice] = "Your changes have been successfully committed"
redirect_to project_tree_path(@project, @ref) redirect_to project_tree_path(@project, @ref)
else else
flash[:alert] = result[:error] flash[:alert] = result[:error]
......
...@@ -16,7 +16,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -16,7 +16,7 @@ class Projects::CommitsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb
format.js format.json { pager_json("projects/commits/_commits", @commits.size) }
format.atom { render layout: false } format.atom { render layout: false }
end end
end end
......
...@@ -7,7 +7,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -7,7 +7,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
layout "project_settings" layout "project_settings"
def index def index
@enabled_keys = @project.deploy_keys.all @enabled_keys = @project.deploy_keys
@available_keys = available_keys - @enabled_keys @available_keys = available_keys - @enabled_keys
end end
......
...@@ -10,7 +10,7 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -10,7 +10,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
result = Files::UpdateContext.new(@project, current_user, params, @ref, @path).execute result = Files::UpdateContext.new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully commited" flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, @id) redirect_to project_blob_path(@project, @id)
else else
flash[:alert] = result[:error] flash[:alert] = result[:error]
......
...@@ -7,7 +7,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -7,7 +7,7 @@ class Projects::HooksController < Projects::ApplicationController
layout "project_settings" layout "project_settings"
def index def index
@hooks = @project.hooks.all @hooks = @project.hooks
@hook = ProjectHook.new @hook = ProjectHook.new
end end
...@@ -18,7 +18,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -18,7 +18,7 @@ class Projects::HooksController < Projects::ApplicationController
if @hook.valid? if @hook.valid?
redirect_to project_hooks_path(@project) redirect_to project_hooks_path(@project)
else else
@hooks = @project.hooks.all @hooks = @project.hooks
render :index render :index
end end
end end
......
...@@ -11,7 +11,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -11,7 +11,7 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow modify issue # Allow modify issue
before_filter :authorize_modify_issue!, only: [:edit, :update] before_filter :authorize_modify_issue!, only: [:edit, :update]
respond_to :js, :html respond_to :html
def index def index
terms = params['issue_search'] terms = params['issue_search']
...@@ -23,11 +23,18 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -23,11 +23,18 @@ class Projects::IssuesController < Projects::ApplicationController
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
@assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? @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?
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html
format.js
format.atom { render layout: false } format.atom { render layout: false }
format.json do
render json: {
html: view_to_html_string("projects/issues/_issues")
}
end
end end
end end
...@@ -45,10 +52,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -45,10 +52,7 @@ class Projects::IssuesController < Projects::ApplicationController
@target_type = :issue @target_type = :issue
@target_id = @issue.id @target_id = @issue.id
respond_to do |format| respond_with(@issue)
format.html
format.js
end
end end
def create def create
...@@ -70,6 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -70,6 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController
def update def update
@issue.update_attributes(params[:issue].merge(author_id_of_changes: current_user.id)) @issue.update_attributes(params[:issue].merge(author_id_of_changes: current_user.id))
@issue.reset_events_cache
respond_to do |format| respond_to do |format|
format.js format.js
......
...@@ -2,8 +2,8 @@ require 'gitlab/satellite/satellite' ...@@ -2,8 +2,8 @@ require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :module_enabled before_filter :module_enabled
before_filter :merge_request, only: [:edit, :update, :show, :commits, :diffs, :automerge, :automerge_check, :ci_status] before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status]
before_filter :closes_issues, only: [:edit, :update, :show, :commits, :diffs] before_filter :closes_issues, only: [:edit, :update, :show, :diffs]
before_filter :validates_merge_request, only: [:show, :diffs] before_filter :validates_merge_request, only: [:show, :diffs]
before_filter :define_show_vars, only: [:show, :diffs] before_filter :define_show_vars, only: [:show, :diffs]
...@@ -26,8 +26,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -26,8 +26,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html format.html
format.js
format.diff { render text: @merge_request.to_diff(current_user) } format.diff { render text: @merge_request.to_diff(current_user) }
format.patch { render text: @merge_request.to_patch(current_user) } format.patch { render text: @merge_request.to_patch(current_user) }
end end
...@@ -44,6 +42,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -44,6 +42,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
diff_line_count = Commit::diff_line_count(@merge_request.diffs) diff_line_count = Commit::diff_line_count(@merge_request.diffs)
@suppress_diff = Commit::diff_suppress?(@merge_request.diffs, diff_line_count) && !params[:force_show_diff] @suppress_diff = Commit::diff_suppress?(@merge_request.diffs, diff_line_count) && !params[:force_show_diff]
@force_suppress_diff = Commit::diff_force_suppress?(@merge_request.diffs, diff_line_count) @force_suppress_diff = Commit::diff_force_suppress?(@merge_request.diffs, diff_line_count)
respond_to do |format|
format.html
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
end
end end
def new def new
...@@ -76,9 +79,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -76,9 +79,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def update def update
# If we close MergeRequest we want to ignore validation
# so we can close broken one (Ex. fork project removed)
if params[:merge_request] == {"state_event"=>"close"}
@merge_request.allow_broken = true
if @merge_request.close
opts = { notice: 'Merge request was successfully closed.' }
else
opts = { alert: 'Failed to close merge request.' }
end
redirect_to [@merge_request.target_project, @merge_request], opts
return
end
# We dont allow change of source/target projects
# after merge request was created
params[:merge_request].delete(:source_project_id)
params[:merge_request].delete(:target_project_id)
if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id)) if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id))
@merge_request.reload_code @merge_request.reload_code
@merge_request.mark_as_unchecked @merge_request.mark_as_unchecked
@merge_request.reset_events_cache
redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.' redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
else else
render "edit" render "edit"
...@@ -157,14 +181,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -157,14 +181,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def validates_merge_request def validates_merge_request
# If source project was removed (Ex. mr from fork to origin)
return invalid_mr unless @merge_request.source_project
# Show git not found page # Show git not found page
# if there is no saved commits between source & target branch # if there is no saved commits between source & target branch
if @merge_request.commits.blank? if @merge_request.commits.blank?
# and if source target doesn't exist # and if target branch doesn't exist
return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch) return invalid_mr unless @merge_request.target_branch_exists?
# or if source branch doesn't exist # or if source branch doesn't exist
return invalid_mr unless @merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) return invalid_mr unless @merge_request.source_branch_exists?
end end
end end
......
...@@ -34,11 +34,6 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -34,11 +34,6 @@ class Projects::MilestonesController < Projects::ApplicationController
@issues = @milestone.issues @issues = @milestone.issues
@users = @milestone.participants.uniq @users = @milestone.participants.uniq
@merge_requests = @milestone.merge_requests @merge_requests = @milestone.merge_requests
respond_to do |format|
format.html
format.js
end
end end
def create def create
......
...@@ -9,7 +9,7 @@ class Projects::NewTreeController < Projects::BaseTreeController ...@@ -9,7 +9,7 @@ class Projects::NewTreeController < Projects::BaseTreeController
result = Files::CreateContext.new(@project, current_user, params, @ref, file_path).execute result = Files::CreateContext.new(@project, current_user, params, @ref, file_path).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully commited" flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path)) redirect_to project_blob_path(@project, File.join(@ref, file_path))
else else
flash[:alert] = result[:error] flash[:alert] = result[:error]
......
...@@ -14,7 +14,14 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -14,7 +14,14 @@ class Projects::NotesController < Projects::ApplicationController
@discussions = discussions_from_notes @discussions = discussions_from_notes
end end
respond_with(@notes) respond_to do |format|
format.html { redirect_to :back }
format.json do
render json: {
html: view_to_html_string("projects/notes/_notes")
}
end
end
end end
def create def create
...@@ -32,6 +39,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -32,6 +39,7 @@ class Projects::NotesController < Projects::ApplicationController
@note = @project.notes.find(params[:id]) @note = @project.notes.find(params[:id])
return access_denied! unless can?(current_user, :admin_note, @note) return access_denied! unless can?(current_user, :admin_note, @note)
@note.destroy @note.destroy
@note.reset_events_cache
respond_to do |format| respond_to do |format|
format.js { render nothing: true } format.js { render nothing: true }
...@@ -43,6 +51,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -43,6 +51,7 @@ class Projects::NotesController < Projects::ApplicationController
return access_denied! unless can?(current_user, :admin_note, @note) return access_denied! unless can?(current_user, :admin_note, @note)
@note.update_attributes(params[:note]) @note.update_attributes(params[:note])
@note.reset_events_cache
respond_to do |format| respond_to do |format|
format.js do format.js do
......
...@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController ...@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create] before_filter :authorize_read_project!, except: [:index, :new, :create]
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph] before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'navless', only: [:new, :create, :fork] layout 'navless', only: [:new, :create, :fork]
...@@ -55,7 +55,7 @@ class ProjectsController < ApplicationController ...@@ -55,7 +55,7 @@ class ProjectsController < ApplicationController
end end
def show def show
return authenticate_user! unless @project.public || current_user return authenticate_user! unless @project.public? || current_user
limit = (params[:limit] || 20).to_i limit = (params[:limit] || 20).to_i
@events = @project.events.recent @events = @project.events.recent
...@@ -73,7 +73,7 @@ class ProjectsController < ApplicationController ...@@ -73,7 +73,7 @@ class ProjectsController < ApplicationController
render :show, layout: user_layout render :show, layout: user_layout
end end
end end
format.js format.json { pager_json("events/_events", @events.count) }
end end
end end
...@@ -116,6 +116,24 @@ class ProjectsController < ApplicationController ...@@ -116,6 +116,24 @@ class ProjectsController < ApplicationController
end end
end end
def archive
return access_denied! unless can?(current_user, :archive_project, project)
project.archive!
respond_to do |format|
format.html { redirect_to @project }
end
end
def unarchive
return access_denied! unless can?(current_user, :archive_project, project)
project.unarchive!
respond_to do |format|
format.html { redirect_to @project }
end
end
private private
def set_title def set_title
......
...@@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController ...@@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController
layout 'public' layout 'public'
def index def index
@projects = Project.public_only @projects = Project.public_or_internal_only(current_user)
@projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
end end
......
...@@ -14,7 +14,7 @@ class SearchController < ApplicationController ...@@ -14,7 +14,7 @@ class SearchController < ApplicationController
project_ids.select! { |id| id == project_id.to_i} project_ids.select! { |id| id == project_id.to_i}
end end
result = SearchContext.new(project_ids, params).execute result = SearchContext.new(project_ids, current_user, params).execute
@projects = result[:projects] @projects = result[:projects]
@merge_requests = result[:merge_requests] @merge_requests = result[:merge_requests]
......
...@@ -62,7 +62,7 @@ module ApplicationHelper ...@@ -62,7 +62,7 @@ module ApplicationHelper
size = 40 if size.nil? || size <= 0 size = 40 if size.nil? || size <= 0
if !Gitlab.config.gravatar.enabled || user_email.blank? if !Gitlab.config.gravatar.enabled || user_email.blank?
'no_avatar.png' '/assets/no_avatar.png'
else else
gravatar_url = request.ssl? || gitlab_config.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url gravatar_url = request.ssl? || gitlab_config.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
user_email.strip! user_email.strip!
...@@ -72,7 +72,7 @@ module ApplicationHelper ...@@ -72,7 +72,7 @@ module ApplicationHelper
def last_commit(project) def last_commit(project)
if project.repo_exists? if project.repo_exists?
time_ago_in_words(project.repository.commit.committed_date) + " ago" time_ago_with_tooltip(project.repository.commit.committed_date) + " ago"
else else
"Never" "Never"
end end
...@@ -136,9 +136,9 @@ module ApplicationHelper ...@@ -136,9 +136,9 @@ module ApplicationHelper
Digest::SHA1.hexdigest string Digest::SHA1.hexdigest string
end end
def project_last_activity project def project_last_activity(project)
if project.last_activity_at if project.last_activity_at
time_ago_in_words(project.last_activity_at) + " ago" time_ago_with_tooltip(project.last_activity_at, 'bottom', 'last_activity_time_ago') + " ago"
else else
"Never" "Never"
end end
...@@ -207,4 +207,22 @@ module ApplicationHelper ...@@ -207,4 +207,22 @@ module ApplicationHelper
def broadcast_message def broadcast_message
BroadcastMessage.current BroadcastMessage.current
end end
def highlight_js(&block)
string = capture(&block)
content_tag :div, class: user_color_scheme_class do
Pygments::Lexer[:js].highlight(string).html_safe
end
end
def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
capture_haml do
haml_tag :time, time_ago_in_words(date),
class: html_class, datetime: date, title: date.stamp("Aug 21, 2011 9:23pm"),
data: { toggle: 'tooltip', placement: placement }
haml_tag :script, "$('." + html_class + "').tooltip()"
end.html_safe
end
end end
...@@ -105,6 +105,10 @@ module CommitsHelper ...@@ -105,6 +105,10 @@ module CommitsHelper
branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
end end
def get_old_file(project, commit, diff)
project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
end
protected protected
# Private: Returns a link to a person. If the person has a matching user and # Private: Returns a link to a person. If the person has a matching user and
......
module CompareHelper module CompareHelper
def compare_to_mr_button? def compare_to_mr_button?
params[:from].present? && params[:to].present? && @project.merge_requests_enabled &&
params[:from].present? &&
params[:to].present? &&
@repository.branch_names.include?(params[:from]) && @repository.branch_names.include?(params[:from]) &&
@repository.branch_names.include?(params[:to]) && @repository.branch_names.include?(params[:to]) &&
params[:from] != params[:to] && params[:from] != params[:to] &&
......
...@@ -102,15 +102,11 @@ module EventsHelper ...@@ -102,15 +102,11 @@ module EventsHelper
end end
elsif event.note_project_snippet? elsif event.note_project_snippet?
link_to(project_snippet_path(event.project, event.note_target)) do link_to(project_snippet_path(event.project, event.note_target)) do
content_tag :strong do "#{event.note_target_type} ##{truncate event.note_target_id}"
"#{event.note_target_type} ##{truncate event.note_target_id}"
end
end end
else else
link_to event_note_target_path(event) do link_to event_note_target_path(event) do
content_tag :strong do "#{event.note_target_type} ##{truncate event.note_target_iid}"
"#{event.note_target_type} ##{truncate event.note_target_iid}"
end
end end
end end
elsif event.wall_note? elsif event.wall_note?
......
...@@ -8,10 +8,14 @@ module IconsHelper ...@@ -8,10 +8,14 @@ module IconsHelper
end end
def public_icon def public_icon
content_tag :i, nil, class: 'icon-globe cblue' content_tag :i, nil, class: 'icon-globe'
end
def internal_icon
content_tag :i, nil, class: 'icon-shield'
end end
def private_icon def private_icon
content_tag :i, nil, class: 'icon-lock cgreen' content_tag :i, nil, class: 'icon-lock'
end end
end end
...@@ -68,4 +68,12 @@ module IssuesHelper ...@@ -68,4 +68,12 @@ module IssuesHelper
false false
end end
end end
def bulk_update_milestone_options
options_for_select(["None (backlog)", nil]) + options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id])
end
def bulk_update_assignee_options
options_for_select(["None (unassigned)", nil]) + options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id])
end
end end
...@@ -36,7 +36,7 @@ module MergeRequestsHelper ...@@ -36,7 +36,7 @@ module MergeRequestsHelper
def merge_path_description(merge_request, separator) def merge_path_description(merge_request, separator)
if merge_request.for_fork? if merge_request.for_fork?
"Project:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}" "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
else else
"Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}" "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
end end
......
...@@ -31,8 +31,14 @@ module NotesHelper ...@@ -31,8 +31,14 @@ module NotesHelper
def note_timestamp(note) def note_timestamp(note)
# Shows the created at time and the updated at time if different # Shows the created at time and the updated at time if different
ts = "#{time_ago_in_words(note.created_at)} ago" ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')} ago"
ts << content_tag(:small, " (Edited #{time_ago_in_words(note.updated_at)} ago)") if note.updated_at != note.created_at if note.updated_at != note.created_at
ts << capture_haml do
haml_tag :small do
haml_concat " (Edited #{time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')} ago)"
end
end
end
ts.html_safe ts.html_safe
end end
end end
...@@ -70,6 +70,8 @@ module ProjectsHelper ...@@ -70,6 +70,8 @@ module ProjectsHelper
scope: params[:scope], scope: params[:scope],
label_name: params[:label_name], label_name: params[:label_name],
milestone_id: params[:milestone_id], milestone_id: params[:milestone_id],
assignee_id: params[:assignee_id],
sort: params[:sort],
} }
options = exist_opts.merge(options) options = exist_opts.merge(options)
...@@ -80,7 +82,7 @@ module ProjectsHelper ...@@ -80,7 +82,7 @@ module ProjectsHelper
end end
def project_active_milestones def project_active_milestones
@project.milestones.active.order("due_date, title ASC").all @project.milestones.active.order("due_date, title ASC")
end end
def project_issues_trackers(current_tracker = nil) def project_issues_trackers(current_tracker = nil)
...@@ -135,8 +137,8 @@ module ProjectsHelper ...@@ -135,8 +137,8 @@ module ProjectsHelper
end end
end end
def repository_size def repository_size(project = nil)
"#{@project.repository.size} MB" "#{(project || @project).repository.size} MB"
rescue rescue
# In order to prevent 500 error # In order to prevent 500 error
# when application cannot allocate memory # when application cannot allocate memory
...@@ -177,4 +179,12 @@ module ProjectsHelper ...@@ -177,4 +179,12 @@ module ProjectsHelper
title title
end end
def default_url_to_repo
current_user ? @project.url_to_repo : @project.http_url_to_repo
end
def default_clone_protocol
current_user ? "ssh" : "http"
end
end end
module SearchHelper module SearchHelper
def search_autocomplete_source def search_autocomplete_source
return unless current_user return unless current_user
[ [
groups_autocomplete, groups_autocomplete,
projects_autocomplete, projects_autocomplete,
public_projects_autocomplete,
default_autocomplete, default_autocomplete,
project_autocomplete, project_autocomplete,
help_autocomplete help_autocomplete
].flatten.to_json ].flatten.uniq do |item|
item[:label]
end.to_json
end end
private private
...@@ -71,7 +73,14 @@ module SearchHelper ...@@ -71,7 +73,14 @@ module SearchHelper
# Autocomplete results for the current user's projects # Autocomplete results for the current user's projects
def projects_autocomplete def projects_autocomplete
current_user.authorized_projects.map do |p| current_user.authorized_projects.non_archived.map do |p|
{ label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
end
end
# Autocomplete results for the current user's projects
def public_projects_autocomplete
Project.public_or_internal_only(current_user).non_archived.map do |p|
{ label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
end end
end end
......
module VisibilityLevelHelper
def visibility_level_color(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
'cgreen'
when Gitlab::VisibilityLevel::INTERNAL
'camber'
when Gitlab::VisibilityLevel::PUBLIC
'cblue'
end
end
def visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "Project access must be granted explicitly for each user."
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The project can be cloned by"
haml_concat "any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The project can be cloned"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def visibility_level_icon(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
private_icon
when Gitlab::VisibilityLevel::INTERNAL
internal_icon
when Gitlab::VisibilityLevel::PUBLIC
public_icon
end
end
def visibility_level_label(level)
Project.visibility_levels.key(level)
end
def restricted_visibility_levels
current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels
end
end
...@@ -2,23 +2,27 @@ module Emails ...@@ -2,23 +2,27 @@ module Emails
module MergeRequests module MergeRequests
def new_merge_request_email(recipient_id, merge_request_id) def new_merge_request_email(recipient_id, merge_request_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title)) mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title))
end end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id
@project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title)) mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title))
end end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@updated_by = User.find updated_by_user_id @updated_by = User.find updated_by_user_id
@project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title)) mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title))
end end
def merged_merge_request_email(recipient_id, merge_request_id) def merged_merge_request_email(recipient_id, merge_request_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title)) mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title))
end end
end end
......
...@@ -13,5 +13,15 @@ module Emails ...@@ -13,5 +13,15 @@ module Emails
mail(to: @user.email, mail(to: @user.email,
subject: subject("Project was moved")) subject: subject("Project was moved"))
end end
def repository_push_email(project_id, recipient, author_id, branch, compare)
@project = Project.find(project_id)
@author = User.find(author_id)
@commits = Commit.decorate(compare.commits)
@diffs = compare.diffs
@branch = branch
mail(to: recipient, subject: subject("New push to repository"))
end
end end
end end
...@@ -16,6 +16,7 @@ class Notify < ActionMailer::Base ...@@ -16,6 +16,7 @@ class Notify < ActionMailer::Base
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
default from: Gitlab.config.gitlab.email_from default from: Gitlab.config.gitlab.email_from
default reply_to: "noreply@#{Gitlab.config.gitlab.host}"
# Just send email with 3 seconds delay # Just send email with 3 seconds delay
def self.delay def self.delay
......
...@@ -29,7 +29,7 @@ class Ability ...@@ -29,7 +29,7 @@ class Ability
nil nil
end end
if project && project.public if project && project.public?
[ [
:read_project, :read_project,
:read_wiki, :read_wiki,
...@@ -59,37 +59,41 @@ class Ability ...@@ -59,37 +59,41 @@ class Ability
# Rules based on role in project # Rules based on role in project
if team.masters.include?(user) if team.masters.include?(user)
rules << project_master_rules rules += project_master_rules
elsif team.developers.include?(user) elsif team.developers.include?(user)
rules << project_dev_rules rules += project_dev_rules
elsif team.reporters.include?(user) elsif team.reporters.include?(user)
rules << project_report_rules rules += project_report_rules
elsif team.guests.include?(user) elsif team.guests.include?(user)
rules << project_guest_rules rules += project_guest_rules
end end
if project.public? if project.public? || project.internal?
rules << public_project_rules rules += public_project_rules
end end
if project.owner == user || user.admin? if project.owner == user || user.admin?
rules << project_admin_rules rules += project_admin_rules
end end
if project.group && project.group.has_owner?(user) if project.group && project.group.has_owner?(user)
rules << project_admin_rules rules += project_admin_rules
end end
rules.flatten if project.archived?
rules -= project_archived_rules
end
rules
end end
def public_project_rules def public_project_rules
project_guest_rules + [ project_guest_rules + [
:download_code, :download_code,
:fork_project, :fork_project
] ]
end end
...@@ -125,6 +129,16 @@ class Ability ...@@ -125,6 +129,16 @@ class Ability
] ]
end end
def project_archived_rules
[
:write_merge_request,
:push_code,
:push_code_to_protected_branches,
:modify_merge_request,
:admin_merge_request
]
end
def project_master_rules def project_master_rules
project_dev_rules + [ project_dev_rules + [
:push_code_to_protected_branches, :push_code_to_protected_branches,
...@@ -145,9 +159,10 @@ class Ability ...@@ -145,9 +159,10 @@ class Ability
def project_admin_rules def project_admin_rules
project_master_rules + [ project_master_rules + [
:change_namespace, :change_namespace,
:change_public_mode, :change_visibility_level,
:rename_project, :rename_project,
:remove_project :remove_project,
:archive_project
] ]
end end
...@@ -160,7 +175,7 @@ class Ability ...@@ -160,7 +175,7 @@ class Ability
# Only group owner and administrators can manage group # Only group owner and administrators can manage group
if group.has_owner?(user) || user.admin? if group.has_owner?(user) || user.admin?
rules << [ rules += [
:manage_group, :manage_group,
:manage_namespace :manage_namespace
] ]
...@@ -174,7 +189,7 @@ class Ability ...@@ -174,7 +189,7 @@ class Ability
# Only namespace owner and administrators can manage it # Only namespace owner and administrators can manage it
if namespace.owner == user || user.admin? if namespace.owner == user || user.admin?
rules << [ rules += [
:manage_namespace :manage_namespace
] ]
end end
......
...@@ -111,4 +111,11 @@ module Issuable ...@@ -111,4 +111,11 @@ module Issuable
end end
users.concat(mentions.reduce([], :|)).uniq users.concat(mentions.reduce([], :|)).uniq
end end
def to_hook_data
{
object_kind: self.class.name.underscore,
object_attributes: self.attributes
}
end
end end
...@@ -18,7 +18,7 @@ class Event < ActiveRecord::Base ...@@ -18,7 +18,7 @@ class Event < ActiveRecord::Base
attr_accessible :project, :action, :data, :author_id, :project_id, attr_accessible :project, :action, :data, :author_id, :project_id,
:target_id, :target_type :target_id, :target_type
default_scope where("author_id IS NOT NULL") default_scope { where.not(author_id: nil) }
CREATED = 1 CREATED = 1
UPDATED = 2 UPDATED = 2
...@@ -223,7 +223,7 @@ class Event < ActiveRecord::Base ...@@ -223,7 +223,7 @@ class Event < ActiveRecord::Base
# Max 20 commits from push DESC # Max 20 commits from push DESC
def commits def commits
@commits ||= data[:commits].reverse @commits ||= (data[:commits] || []).reverse
end end
def commits_count def commits_count
......
...@@ -33,7 +33,7 @@ class GollumWiki ...@@ -33,7 +33,7 @@ class GollumWiki
end end
def http_url_to_repo def http_url_to_repo
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end end
# Returns the Gollum::Wiki object. # Returns the Gollum::Wiki object.
......
...@@ -36,7 +36,7 @@ class Group < Namespace ...@@ -36,7 +36,7 @@ class Group < Namespace
def add_users(user_ids, group_access) def add_users(user_ids, group_access)
user_ids.compact.each do |user_id| user_ids.compact.each do |user_id|
user = self.users_groups.find_or_initialize_by_user_id(user_id) user = self.users_groups.find_or_initialize_by(user_id: user_id)
user.update_attributes(group_access: group_access) user.update_attributes(group_access: group_access)
end end
end end
......
...@@ -28,7 +28,7 @@ class Issue < ActiveRecord::Base ...@@ -28,7 +28,7 @@ class Issue < ActiveRecord::Base
scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :opened, -> { with_state(:opened) } scope :opened, -> { with_state(:opened, :reopened) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
attr_accessible :title, :assignee_id, :position, :description, attr_accessible :title, :assignee_id, :position, :description,
...@@ -56,12 +56,23 @@ class Issue < ActiveRecord::Base ...@@ -56,12 +56,23 @@ class Issue < ActiveRecord::Base
state :closed state :closed
end end
# Both open and reopened issues should be listed as opened
scope :opened, -> { with_state(:opened, :reopened) }
# Mentionable overrides. # Mentionable overrides.
def gfm_reference def gfm_reference
"issue ##{iid}" "issue ##{iid}"
end end
# Reset issue events cache
#
# Since we do cache @event we need to reset cache in special cases:
# * when an issue is updated
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.where(target_id: self.id, target_type: 'Issue').
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
end end
...@@ -35,6 +35,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -35,6 +35,10 @@ class MergeRequest < ActiveRecord::Base
attr_accessor :should_remove_source_branch attr_accessor :should_remove_source_branch
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
attr_accessor :allow_broken
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
transition [:reopened, :opened] => :closed transition [:reopened, :opened] => :closed
...@@ -80,7 +84,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -80,7 +84,7 @@ class MergeRequest < ActiveRecord::Base
serialize :st_commits serialize :st_commits
serialize :st_diffs serialize :st_diffs
validates :source_project, presence: true validates :source_project, presence: true, unless: :allow_broken
validates :source_branch, presence: true validates :source_branch, presence: true
validates :target_project, presence: true validates :target_project, presence: true
validates :target_branch, presence: true validates :target_branch, presence: true
...@@ -262,7 +266,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -262,7 +266,7 @@ class MergeRequest < ActiveRecord::Base
# Return the set of issues that will be closed if this merge request is accepted. # Return the set of issues that will be closed if this merge request is accepted.
def closes_issues def closes_issues
if target_branch == project.default_branch if target_branch == project.default_branch
unmerged_commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id) commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id)
else else
[] []
end end
...@@ -273,6 +277,48 @@ class MergeRequest < ActiveRecord::Base ...@@ -273,6 +277,48 @@ class MergeRequest < ActiveRecord::Base
"merge request !#{iid}" "merge request !#{iid}"
end end
def target_project_path
if target_project
target_project.path_with_namespace
else
"(removed)"
end
end
def source_project_path
if source_project
source_project.path_with_namespace
else
"(removed)"
end
end
def source_branch_exists?
return false unless self.source_project
self.source_project.repository.branch_names.include?(self.source_branch)
end
def target_branch_exists?
return false unless self.target_project
self.target_project.repository.branch_names.include?(self.target_branch)
end
# Reset merge request events cache
#
# Since we do cache @event we need to reset cache in special cases:
# * when a merge request is updated
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.where(target_id: self.id, target_type: 'MergeRequest').
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
private private
def dump_commits(commits) def dump_commits(commits)
......
...@@ -157,7 +157,8 @@ class Note < ActiveRecord::Base ...@@ -157,7 +157,8 @@ class Note < ActiveRecord::Base
# otherwise false is returned # otherwise false is returned
def downvote? def downvote?
votable? && (note.start_with?('-1') || votable? && (note.start_with?('-1') ||
note.start_with?(':-1:') note.start_with?(':-1:') ||
note.start_with?(':thumbsdown:')
) )
end end
...@@ -206,7 +207,8 @@ class Note < ActiveRecord::Base ...@@ -206,7 +207,8 @@ class Note < ActiveRecord::Base
# otherwise false is returned # otherwise false is returned
def upvote? def upvote?
votable? && (note.start_with?('+1') || votable? && (note.start_with?('+1') ||
note.start_with?(':+1:') note.start_with?(':+1:') ||
note.start_with?(':thumbsup:')
) )
end end
...@@ -237,4 +239,19 @@ class Note < ActiveRecord::Base ...@@ -237,4 +239,19 @@ class Note < ActiveRecord::Base
def noteable_type=(sType) def noteable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s) super(sType.to_s.classify.constantize.base_class.to_s)
end end
# Reset notes events cache
#
# Since we do cache @event we need to reset cache in special cases:
# * when a note is updated
# * when a note is removed
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.where(target_id: self.id, target_type: 'Note').
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
end end
...@@ -14,24 +14,25 @@ ...@@ -14,24 +14,25 @@
# merge_requests_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null
# wiki_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null
# namespace_id :integer # namespace_id :integer
# public :boolean default(FALSE), not null
# issues_tracker :string(255) default("gitlab"), not null # issues_tracker :string(255) default("gitlab"), not null
# issues_tracker_id :string(255) # issues_tracker_id :string(255)
# snippets_enabled :boolean default(TRUE), not null # snippets_enabled :boolean default(TRUE), not null
# last_activity_at :datetime # last_activity_at :datetime
# imported :boolean default(FALSE), not null # imported :boolean default(FALSE), not null
# import_url :string(255) # import_url :string(255)
# visibility_level :integer default(0), not null
# #
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
extend Enumerize extend Enumerize
ActsAsTaggableOn.strict_case_match = true ActsAsTaggableOn.strict_case_match = true
attr_accessible :name, :path, :description, :issues_tracker, :label_list, attr_accessible :name, :path, :description, :issues_tracker, :label_list,
:issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id,
:wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin] :wiki_enabled, :visibility_level, :import_url, :last_activity_at, as: [:default, :admin]
attr_accessible :namespace_id, :creator_id, as: :admin attr_accessible :namespace_id, :creator_id, as: :admin
...@@ -41,23 +42,29 @@ class Project < ActiveRecord::Base ...@@ -41,23 +42,29 @@ class Project < ActiveRecord::Base
# Relations # Relations
belongs_to :creator, foreign_key: "creator_id", class_name: "User" belongs_to :creator, foreign_key: "creator_id", class_name: "User"
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id"
belongs_to :namespace belongs_to :namespace
has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
has_one :gitlab_ci_service, dependent: :destroy has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy has_one :campfire_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy
has_one :hipchat_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy
has_one :flowdock_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy
has_one :assembla_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
has_many :services, dependent: :destroy has_many :services, dependent: :destroy
has_many :events, dependent: :destroy has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
has_many :fork_merge_requests,dependent: :destroy, foreign_key: "source_project_id", class_name: MergeRequest
has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC"
has_many :milestones, dependent: :destroy has_many :milestones, dependent: :destroy
has_many :notes, dependent: :destroy has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
...@@ -77,8 +84,8 @@ class Project < ActiveRecord::Base ...@@ -77,8 +84,8 @@ class Project < ActiveRecord::Base
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
# Validations # Validations
validates :creator, presence: true validates :creator, presence: true, on: :create
validates :description, length: { within: 0..2000 } validates :description, length: { maximum: 2000 }, allow_blank: true
validates :name, presence: true, length: { within: 0..255 }, validates :name, presence: true, length: { within: 0..255 },
format: { with: Gitlab::Regex.project_name_regex, format: { with: Gitlab::Regex.project_name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter or digit should be first" } message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter or digit should be first" }
...@@ -88,7 +95,7 @@ class Project < ActiveRecord::Base ...@@ -88,7 +95,7 @@ class Project < ActiveRecord::Base
message: "only letters, digits & '_' '-' '.' allowed. Letter or digit should be first" } message: "only letters, digits & '_' '-' '.' allowed. Letter or digit should be first" }
validates :issues_enabled, :wall_enabled, :merge_requests_enabled, validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] } :wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { within: 0..255 } validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
validates :namespace, presence: true validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
...@@ -110,7 +117,10 @@ class Project < ActiveRecord::Base ...@@ -110,7 +117,10 @@ class Project < ActiveRecord::Base
scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
scope :public_only, -> { where(public: true) } scope :public_only, -> { where(visibility_level: PUBLIC) }
scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) }
scope :non_archived, -> { where(archived: false) }
enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
...@@ -128,7 +138,7 @@ class Project < ActiveRecord::Base ...@@ -128,7 +138,7 @@ class Project < ActiveRecord::Base
end end
def search query def search query
joins(:namespace).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%")
end end
def find_with_namespace(id) def find_with_namespace(id)
...@@ -142,6 +152,10 @@ class Project < ActiveRecord::Base ...@@ -142,6 +152,10 @@ class Project < ActiveRecord::Base
where(path: id, namespace_id: nil).last where(path: id, namespace_id: nil).last
end end
end end
def visibility_levels
Gitlab::VisibilityLevel.options
end
end end
def team def team
...@@ -227,7 +241,7 @@ class Project < ActiveRecord::Base ...@@ -227,7 +241,7 @@ class Project < ActiveRecord::Base
end end
def available_services_names def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock) %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push)
end end
def gitlab_ci? def gitlab_ci?
...@@ -294,8 +308,10 @@ class Project < ActiveRecord::Base ...@@ -294,8 +308,10 @@ class Project < ActiveRecord::Base
ProjectTransferService.new.transfer(self, new_namespace) ProjectTransferService.new.transfer(self, new_namespace)
end end
def execute_hooks(data) def execute_hooks(data, hooks_scope = :push_hooks)
hooks.each { |hook| hook.async_execute(data) } hooks.send(hooks_scope).each do |hook|
hook.async_execute(data)
end
end end
def execute_services(data) def execute_services(data)
...@@ -312,14 +328,14 @@ class Project < ActiveRecord::Base ...@@ -312,14 +328,14 @@ class Project < ActiveRecord::Base
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id) c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
# Update code for merge requests into project between project branches # Update code for merge requests into project between project branches
mrs = self.merge_requests.opened.by_branch(branch_name).all mrs = self.merge_requests.opened.by_branch(branch_name).to_a
# Update code for merge requests between project and project fork # Update code for merge requests between project and project fork
mrs += self.fork_merge_requests.opened.by_branch(branch_name).all mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
# Close merge requests # Close merge requests
mrs = self.merge_requests.opened.where(target_branch: branch_name).all mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
mrs.each { |merge_request| merge_request.merge!(user.id) } mrs.each { |merge_request| merge_request.merge!(user.id) }
...@@ -388,7 +404,7 @@ class Project < ActiveRecord::Base ...@@ -388,7 +404,7 @@ class Project < ActiveRecord::Base
end end
def http_url_to_repo def http_url_to_repo
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end end
# Check if current branch name is marked as protected in the system # Check if current branch name is marked as protected in the system
...@@ -453,4 +469,26 @@ class Project < ActiveRecord::Base ...@@ -453,4 +469,26 @@ class Project < ActiveRecord::Base
def default_branch def default_branch
@default_branch ||= repository.root_ref if repository.exists? @default_branch ||= repository.root_ref if repository.exists?
end end
def reload_default_branch
@default_branch = nil
default_branch
end
def visibility_level_field
visibility_level
end
def archive!
update_attribute(:archived, true)
end
def unarchive!
update_attribute(:archived, false)
end
def change_head(branch)
gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch
end
end end
...@@ -2,15 +2,24 @@ ...@@ -2,15 +2,24 @@
# #
# Table name: web_hooks # Table name: web_hooks
# #
# id :integer not null, primary key # id :integer not null, primary key
# url :string(255) # url :string(255)
# project_id :integer # project_id :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# type :string(255) default("ProjectHook") # type :string(255) default("ProjectHook")
# service_id :integer # service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# #
class ProjectHook < WebHook class ProjectHook < WebHook
belongs_to :project belongs_to :project
attr_accessible :push_events, :issues_events, :merge_requests_events
scope :push_hooks, -> { where(push_events: true) }
scope :issue_hooks, -> { where(issues_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
end end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
#
class AssemblaService < Service
include HTTParty
validates :token, presence: true, if: :activated?
def title
'Assembla'
end
def description
'Project Management Software (Source Commits Endpoint)'
end
def to_param
'assembla'
end
def fields
[
{ type: 'text', name: 'token', placeholder: '' }
]
end
def execute(push)
url = "https://atlas.assembla.com/spaces/ouposp/github_tool?secret_key=#{token}"
AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' })
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
#
class EmailsOnPushService < Service
attr_accessible :recipients
validates :recipients, presence: true, if: :activated?
def title
'Emails on push'
end
def description
'Email the commits and diff of each push to a list of recipients.'
end
def to_param
'emails_on_push'
end
def execute(push_data)
EmailsOnPushWorker.perform_async(project_id, recipients, push_data)
end
def fields
[
{ type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' },
]
end
end
...@@ -87,9 +87,8 @@ class ProjectTeam ...@@ -87,9 +87,8 @@ class ProjectTeam
def import(source_project) def import(source_project)
target_project = project target_project = project
source_team = source_project.users_projects.all source_team = source_project.users_projects.to_a
target_team = target_project.users_projects.all target_user_ids = target_project.users_projects.pluck(:user_id)
target_user_ids = target_team.map(&:user_id)
source_team.reject! do |tm| source_team.reject! do |tm|
# Skip if user already present in team # Skip if user already present in team
......
...@@ -133,6 +133,7 @@ class Repository ...@@ -133,6 +133,7 @@ class Repository
Rails.cache.delete(cache_key(:tag_names)) Rails.cache.delete(cache_key(:tag_names))
Rails.cache.delete(cache_key(:commit_count)) Rails.cache.delete(cache_key(:commit_count))
Rails.cache.delete(cache_key(:graph_log)) Rails.cache.delete(cache_key(:graph_log))
Rails.cache.delete(cache_key(:readme))
end end
def graph_log def graph_log
...@@ -159,4 +160,10 @@ class Repository ...@@ -159,4 +160,10 @@ class Repository
def blob_at(sha, path) def blob_at(sha, path)
Gitlab::Git::Blob.find(self, sha, path) Gitlab::Git::Blob.find(self, sha, path)
end end
def readme
Rails.cache.fetch(cache_key(:readme)) do
Tree.new(self, self.root_ref).readme
end
end
end end
...@@ -2,13 +2,16 @@ ...@@ -2,13 +2,16 @@
# #
# Table name: web_hooks # Table name: web_hooks
# #
# id :integer not null, primary key # id :integer not null, primary key
# url :string(255) # url :string(255)
# project_id :integer # project_id :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# type :string(255) default("ProjectHook") # type :string(255) default("ProjectHook")
# service_id :integer # service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# #
class ServiceHook < WebHook class ServiceHook < WebHook
......
...@@ -2,13 +2,16 @@ ...@@ -2,13 +2,16 @@
# #
# Table name: web_hooks # Table name: web_hooks
# #
# id :integer not null, primary key # id :integer not null, primary key
# url :string(255) # url :string(255)
# project_id :integer # project_id :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# type :string(255) default("ProjectHook") # type :string(255) default("ProjectHook")
# service_id :integer # service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# #
class SystemHook < WebHook class SystemHook < WebHook
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
# confirmed_at :datetime # confirmed_at :datetime
# confirmation_sent_at :datetime # confirmation_sent_at :datetime
# unconfirmed_email :string(255) # unconfirmed_email :string(255)
# hide_no_ssh_key :boolean default(FALSE), not null
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -52,7 +53,7 @@ class User < ActiveRecord::Base ...@@ -52,7 +53,7 @@ class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password, :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password,
:extern_uid, :provider, :password_expires_at, :avatar, :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key,
as: [:default, :admin] as: [:default, :admin]
attr_accessible :projects_limit, :can_create_group, attr_accessible :projects_limit, :can_create_group,
...@@ -72,7 +73,7 @@ class User < ActiveRecord::Base ...@@ -72,7 +73,7 @@ class User < ActiveRecord::Base
# #
# Namespace for personal projects # Namespace for personal projects
has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL' has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace"
# Profile # Profile
has_many :keys, dependent: :destroy has_many :keys, dependent: :destroy
...@@ -80,8 +81,7 @@ class User < ActiveRecord::Base ...@@ -80,8 +81,7 @@ class User < ActiveRecord::Base
# Groups # Groups
has_many :users_groups, dependent: :destroy has_many :users_groups, dependent: :destroy
has_many :groups, through: :users_groups has_many :groups, through: :users_groups
has_many :owned_groups, through: :users_groups, source: :group, conditions: { users_groups: { group_access: UsersGroup::OWNER } } has_many :owned_groups, -> { where users_groups: { group_access: UsersGroup::OWNER } }, through: :users_groups, source: :group
# Projects # Projects
has_many :groups_projects, through: :groups, source: :projects has_many :groups_projects, through: :groups, source: :projects
has_many :personal_projects, through: :namespace, source: :projects has_many :personal_projects, through: :namespace, source: :projects
...@@ -94,7 +94,7 @@ class User < ActiveRecord::Base ...@@ -94,7 +94,7 @@ class User < ActiveRecord::Base
has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
has_many :recent_events, foreign_key: :author_id, class_name: "Event", order: "id DESC" has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
...@@ -104,7 +104,7 @@ class User < ActiveRecord::Base ...@@ -104,7 +104,7 @@ class User < ActiveRecord::Base
# #
validates :name, presence: true validates :name, presence: true
validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ } validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ }
validates :bio, length: { within: 0..255 } validates :bio, length: { maximum: 255 }, allow_blank: true
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
validates :username, presence: true, uniqueness: true, validates :username, presence: true, uniqueness: true,
...@@ -164,7 +164,7 @@ class User < ActiveRecord::Base ...@@ -164,7 +164,7 @@ class User < ActiveRecord::Base
scope :alphabetically, -> { order('name ASC') } scope :alphabetically, -> { order('name ASC') }
scope :in_team, ->(team){ where(id: team.member_ids) } scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : scoped } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') }
scope :ldap, -> { where(provider: 'ldap') } scope :ldap, -> { where(provider: 'ldap') }
...@@ -199,7 +199,7 @@ class User < ActiveRecord::Base ...@@ -199,7 +199,7 @@ class User < ActiveRecord::Base
end end
def by_username_or_id(name_or_id) def by_username_or_id(name_or_id)
where('username = ? OR id = ?', name_or_id, name_or_id).first where('users.username = ? OR users.id = ?', name_or_id, name_or_id.to_i).first
end end
def build_user(attrs = {}, options= {}) def build_user(attrs = {}, options= {})
...@@ -377,7 +377,7 @@ class User < ActiveRecord::Base ...@@ -377,7 +377,7 @@ class User < ActiveRecord::Base
end end
def accessible_deploy_keys def accessible_deploy_keys
DeployKey.in_projects(self.authorized_projects).uniq DeployKey.in_projects(self.authorized_projects.pluck(:id)).uniq
end end
def created_by def created_by
...@@ -413,4 +413,18 @@ class User < ActiveRecord::Base ...@@ -413,4 +413,18 @@ class User < ActiveRecord::Base
project.namespace != namespace && project.namespace != namespace &&
project.project_member(self) project.project_member(self)
end end
# Reset project events cache related to this user
#
# Since we do cache @event we need to reset cache in special cases:
# * when the user changes their avatar
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.where(author_id: self.id).
order('id DESC').limit(1000).
update_all(updated_at: Time.now)
end
end end
...@@ -2,13 +2,16 @@ ...@@ -2,13 +2,16 @@
# #
# Table name: web_hooks # Table name: web_hooks
# #
# id :integer not null, primary key # id :integer not null, primary key
# url :string(255) # url :string(255)
# project_id :integer # project_id :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# type :string(255) default("ProjectHook") # type :string(255) default("ProjectHook")
# service_id :integer # service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# #
class WebHook < ActiveRecord::Base class WebHook < ActiveRecord::Base
......
class IssueObserver < BaseObserver class IssueObserver < BaseObserver
def after_create(issue) def after_create(issue)
notification.new_issue(issue, current_user) notification.new_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user) issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
end end
def after_close(issue, transition) def after_close(issue, transition)
notification.close_issue(issue, current_user) notification.close_issue(issue, current_user)
create_note(issue) create_note(issue)
execute_hooks(issue)
end end
def after_reopen(issue, transition) def after_reopen(issue, transition)
create_note(issue) create_note(issue)
execute_hooks(issue)
end end
def after_update(issue) def after_update(issue)
...@@ -21,6 +22,7 @@ class IssueObserver < BaseObserver ...@@ -21,6 +22,7 @@ class IssueObserver < BaseObserver
end end
issue.notice_added_references(issue.project, current_user) issue.notice_added_references(issue.project, current_user)
execute_hooks(issue)
end end
protected protected
...@@ -29,4 +31,8 @@ class IssueObserver < BaseObserver ...@@ -29,4 +31,8 @@ class IssueObserver < BaseObserver
def create_note(issue) def create_note(issue)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit) Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end end
def execute_hooks(issue)
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
end
end end
...@@ -7,15 +7,15 @@ class MergeRequestObserver < ActivityObserver ...@@ -7,15 +7,15 @@ class MergeRequestObserver < ActivityObserver
end end
notification.new_merge_request(merge_request, current_user) notification.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user) merge_request.create_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request)
end end
def after_close(merge_request, transition) def after_close(merge_request, transition)
create_event(merge_request, Event::CLOSED) create_event(merge_request, Event::CLOSED)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
notification.close_mr(merge_request, current_user) notification.close_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
end end
def after_merge(merge_request, transition) def after_merge(merge_request, transition)
...@@ -31,17 +31,21 @@ class MergeRequestObserver < ActivityObserver ...@@ -31,17 +31,21 @@ class MergeRequestObserver < ActivityObserver
action: Event::MERGED, action: Event::MERGED,
author_id: merge_request.author_id_of_changes author_id: merge_request.author_id_of_changes
) )
execute_hooks(merge_request)
end end
def after_reopen(merge_request, transition) def after_reopen(merge_request, transition)
create_event(merge_request, Event::REOPENED) create_event(merge_request, Event::REOPENED)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil) create_note(merge_request)
execute_hooks(merge_request)
end end
def after_update(merge_request) def after_update(merge_request)
notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned? notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
merge_request.notice_added_references(merge_request.project, current_user) merge_request.notice_added_references(merge_request.project, current_user)
execute_hooks(merge_request)
end end
def create_event(record, status) def create_event(record, status)
...@@ -53,4 +57,17 @@ class MergeRequestObserver < ActivityObserver ...@@ -53,4 +57,17 @@ class MergeRequestObserver < ActivityObserver
author_id: current_user.id author_id: current_user.id
) )
end end
private
# Create merge request note with service comment like 'Status changed to closed'
def create_note(merge_request)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
def execute_hooks(merge_request)
if merge_request.project
merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
end
end
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.
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