Commit 5cd06603 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch '6-1-stable-ee' of /home/git/repositories/gitlab/gitlab-ee

parents e68a5b2a da0e80b6
...@@ -9,6 +9,8 @@ coverage/* ...@@ -9,6 +9,8 @@ coverage/*
backups/* backups/*
*.swp *.swp
public/uploads/ public/uploads/
.ruby-version
.ruby-gemset
.rvmrc .rvmrc
.rbenv-version .rbenv-version
.directory .directory
......
--colour --drb --color --drb
v 6.1.0
- Project specific IDs for issues, mr, milestones
Above items will get a new id and for example all bookmarked issue urls will change.
Old issue urls are redirected to the new one if the issue id is too high for an internal id.
- Description field added to Merge Request
- API: Sudo api calls (Izaak Alpert)
- API: Group membership api (Izaak Alpert)
- Improved commit diff
- Improved large commit handling (Boyan Tabakov)
- Rewrite: Init script now less prone to errors and keeps better track of the service (Rovanion Luckey)
- Link issues, merge requests, and commits when they reference each other with GFM (Ash Wilson)
- Close issues automatically when pushing commits with a special message
- Improve user removal from admin area
- Invalidate events cache when project was moved
- Remove deprecated classes and rake tasks
- Add event filter for group and project show pages
- Add links to create branch/tag from project home page
- Add public-project? checkbox to new-project view
- Improved compare page. Added link to proceed into Merge Request
- Send email to user when he was added to group
- New landing page when you have 0 projects
v 6.0.0 v 6.0.0
- Feature: Replace teams with group membership - Feature: Replace teams with group membership
We introduce group membership in 6.0 as a replacement for teams. We introduce group membership in 6.0 as a replacement for teams.
......
...@@ -23,7 +23,7 @@ gem 'omniauth-github' ...@@ -23,7 +23,7 @@ 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", '2.1.0' gem "gitlab_git", '2.3.1'
# 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', '~> 1.0.1', require: 'grack'
...@@ -135,7 +135,7 @@ group :assets do ...@@ -135,7 +135,7 @@ group :assets do
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", git: "https://github.com/gitlabhq/raphael-rails.git" gem "raphael-rails", "~> 2.1.2"
gem 'bootstrap-sass' gem 'bootstrap-sass'
gem "font-awesome-rails" gem "font-awesome-rails"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
...@@ -188,7 +188,7 @@ group :development, :test do ...@@ -188,7 +188,7 @@ group :development, :test do
gem 'rb-inotify', require: linux_only('rb-inotify') gem 'rb-inotify', require: linux_only('rb-inotify')
# PhantomJS driver for Capybara # PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.3.0' gem 'poltergeist', '~> 1.4.1'
gem 'spork', '~> 1.0rc' gem 'spork', '~> 1.0rc'
gem 'jasmine' gem 'jasmine'
......
...@@ -6,12 +6,6 @@ GIT ...@@ -6,12 +6,6 @@ GIT
activerecord (>= 2.3.0) activerecord (>= 2.3.0)
rake (>= 0.8.7) rake (>= 0.8.7)
GIT
remote: https://github.com/gitlabhq/raphael-rails.git
revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
specs:
raphael-rails (2.1.0)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
...@@ -78,6 +72,7 @@ GEM ...@@ -78,6 +72,7 @@ GEM
railties (>= 3.0) railties (>= 3.0)
sass-rails (>= 3.2) sass-rails (>= 3.2)
chunky_png (1.2.8) chunky_png (1.2.8)
cliver (0.2.1)
code_analyzer (0.3.2) code_analyzer (0.3.2)
sexp_processor sexp_processor
coderay (1.0.9) coderay (1.0.9)
...@@ -108,7 +103,7 @@ GEM ...@@ -108,7 +103,7 @@ GEM
d3_rails (3.1.10) d3_rails (3.1.10)
railties (>= 3.1.0) railties (>= 3.1.0)
daemons (1.1.9) daemons (1.1.9)
database_cleaner (1.0.1) database_cleaner (1.1.1)
debug_inspector (0.0.2) debug_inspector (0.0.2)
descendants_tracker (0.0.1) descendants_tracker (0.0.1)
devise (2.2.5) devise (2.2.5)
...@@ -138,8 +133,6 @@ GEM ...@@ -138,8 +133,6 @@ GEM
multipart-post (~> 1.1) multipart-post (~> 1.1)
faraday_middleware (0.9.0) faraday_middleware (0.9.0)
faraday (>= 0.7.4, < 0.9) faraday (>= 0.7.4, < 0.9)
faye-websocket (0.4.7)
eventmachine (>= 0.12.0)
ffaker (1.16.1) ffaker (1.16.1)
ffi (1.9.0) ffi (1.9.0)
fog (1.3.1) fog (1.3.1)
...@@ -186,7 +179,7 @@ GEM ...@@ -186,7 +179,7 @@ GEM
gitlab-pygments.rb (0.3.2) gitlab-pygments.rb (0.3.2)
posix-spawn (~> 0.3.6) posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.1.0) yajl-ruby (~> 1.1.0)
gitlab_git (2.1.0) gitlab_git (2.3.1)
activesupport (~> 3.2.13) activesupport (~> 3.2.13)
github-linguist (~> 2.3.4) github-linguist (~> 2.3.4)
gitlab-grit (~> 2.6.0) gitlab-grit (~> 2.6.0)
...@@ -281,7 +274,7 @@ GEM ...@@ -281,7 +274,7 @@ GEM
mime-types (~> 1.16) mime-types (~> 1.16)
treetop (~> 1.4.8) treetop (~> 1.4.8)
method_source (0.8.1) method_source (0.8.1)
mime-types (1.23) mime-types (1.25)
minitest (4.7.4) minitest (4.7.4)
modernizr (2.6.2) modernizr (2.6.2)
sprockets (~> 2.0) sprockets (~> 2.0)
...@@ -321,10 +314,11 @@ GEM ...@@ -321,10 +314,11 @@ GEM
omniauth-oauth (~> 1.0) omniauth-oauth (~> 1.0)
orm_adapter (0.4.0) orm_adapter (0.4.0)
pg (0.15.1) pg (0.15.1)
poltergeist (1.3.0) poltergeist (1.4.1)
capybara (~> 2.1.0) capybara (~> 2.1.0)
faye-websocket (>= 0.4.4, < 0.5.0) cliver (~> 0.2.1)
http_parser.rb (~> 0.5.3) multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
polyglot (0.3.3) polyglot (0.3.3)
posix-spawn (0.3.6) posix-spawn (0.3.6)
pry (0.9.12.2) pry (0.9.12.2)
...@@ -380,6 +374,7 @@ GEM ...@@ -380,6 +374,7 @@ GEM
thor (>= 0.14.6, < 2.0) thor (>= 0.14.6, < 2.0)
raindrops (0.11.0) raindrops (0.11.0)
rake (10.1.0) rake (10.1.0)
raphael-rails (2.1.2)
rb-fsevent (0.9.3) rb-fsevent (0.9.3)
rb-inotify (0.9.0) rb-inotify (0.9.0)
ffi (>= 0.5.0) ffi (>= 0.5.0)
...@@ -389,24 +384,24 @@ GEM ...@@ -389,24 +384,24 @@ GEM
json (~> 1.4) json (~> 1.4)
redcarpet (2.2.2) redcarpet (2.2.2)
redis (3.0.4) redis (3.0.4)
redis-actionpack (3.2.3) redis-actionpack (3.2.4)
actionpack (~> 3.2.3) actionpack (~> 3.2.0)
redis-rack (~> 1.4.0) redis-rack (~> 1.4.4)
redis-store (~> 1.1.4)
redis-activesupport (3.2.4)
activesupport (~> 3.2.0)
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
redis-activesupport (3.2.3) redis-namespace (1.3.1)
activesupport (~> 3.2.3)
redis-store (~> 1.1.0)
redis-namespace (1.3.0)
redis (~> 3.0.0) redis (~> 3.0.0)
redis-rack (1.4.2) redis-rack (1.4.4)
rack (~> 1.4.1) rack (~> 1.4.0)
redis-store (~> 1.1.0) redis-store (~> 1.1.4)
redis-rails (3.2.3) redis-rails (3.2.4)
redis-actionpack (~> 3.2.3) redis-actionpack (~> 3.2.4)
redis-activesupport (~> 3.2.3) redis-activesupport (~> 3.2.4)
redis-store (~> 1.1.0) redis-store (~> 1.1.4)
redis-store (1.1.3) redis-store (1.1.4)
redis (>= 2.2.0) redis (>= 2.2)
ref (1.0.5) ref (1.0.5)
rest-client (1.6.7) rest-client (1.6.7)
mime-types (>= 1.16) mime-types (>= 1.16)
...@@ -426,7 +421,7 @@ GEM ...@@ -426,7 +421,7 @@ GEM
rspec-expectations (~> 2.13.0) rspec-expectations (~> 2.13.0)
rspec-mocks (~> 2.13.0) rspec-mocks (~> 2.13.0)
ruby-hmac (0.4.0) ruby-hmac (0.4.0)
ruby-progressbar (1.1.1) ruby-progressbar (1.2.0)
rubyntlm (0.1.1) rubyntlm (0.1.1)
rubyzip (0.9.9) rubyzip (0.9.9)
safe_yaml (0.9.3) safe_yaml (0.9.3)
...@@ -455,11 +450,11 @@ GEM ...@@ -455,11 +450,11 @@ GEM
sexp_processor (4.2.1) sexp_processor (4.2.1)
shoulda-matchers (2.1.0) shoulda-matchers (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (2.12.4) sidekiq (2.14.0)
celluloid (>= 0.14.1) celluloid (>= 0.14.1)
connection_pool (>= 1.0.0) connection_pool (>= 1.0.0)
json json
redis (>= 3.0) redis (>= 3.0.4)
redis-namespace redis-namespace
simple_oauth (0.1.9) simple_oauth (0.1.9)
simplecov (0.7.1) simplecov (0.7.1)
...@@ -539,6 +534,7 @@ GEM ...@@ -539,6 +534,7 @@ GEM
addressable (>= 2.2.7) addressable (>= 2.2.7)
crack (>= 0.3.2) crack (>= 0.3.2)
websocket (1.0.7) websocket (1.0.7)
websocket-driver (0.3.0)
xpath (2.0.0) xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
yajl-ruby (1.1.0) yajl-ruby (1.1.0)
...@@ -576,7 +572,7 @@ DEPENDENCIES ...@@ -576,7 +572,7 @@ DEPENDENCIES
gitlab-gollum-lib (~> 1.0.1) gitlab-gollum-lib (~> 1.0.1)
gitlab-grack (~> 1.0.1) gitlab-grack (~> 1.0.1)
gitlab-pygments.rb (~> 0.3.2) gitlab-pygments.rb (~> 0.3.2)
gitlab_git (= 2.1.0) gitlab_git (= 2.3.1)
gitlab_meta (= 6.0) gitlab_meta (= 6.0)
gitlab_omniauth-ldap (= 1.0.3) gitlab_omniauth-ldap (= 1.0.3)
gon gon
...@@ -605,14 +601,14 @@ DEPENDENCIES ...@@ -605,14 +601,14 @@ DEPENDENCIES
omniauth-google-oauth2 omniauth-google-oauth2
omniauth-twitter omniauth-twitter
pg pg
poltergeist (~> 1.3.0) poltergeist (~> 1.4.1)
pry pry
quiet_assets (~> 1.0.1) quiet_assets (~> 1.0.1)
rack-mini-profiler rack-mini-profiler
rails (= 3.2.13) rails (= 3.2.13)
rails-dev-tweaks rails-dev-tweaks
rails_best_practices rails_best_practices
raphael-rails! raphael-rails (~> 2.1.2)
rb-fsevent rb-fsevent
rb-inotify rb-inotify
redcarpet (~> 2.2.2) redcarpet (~> 2.2.2)
......
...@@ -2,23 +2,47 @@ ...@@ -2,23 +2,47 @@
## Purpose of describing the contributing process ## Purpose of describing the contributing process
Below we describe the contributing process for two reasons. Contributors know what to expect from maintainers (initial, response within xx days, friendly treatment, etc). And maintainers know what to expect from contributors (use latest version, confirm the issue is addressed, friendly treatment, etc). Below we describe the contributing process to GitLab for two reasons. So that contributors know what to expect from maintainers (possible responses, friendly treatment, etc.). And so that maintainers know what to expect from contributors (use the latest version, ensure that the issue is addressed, friendly treatment, etc.).
## How we handle issues ## Common actions
The priority should be mentioning people that can help and assigning workflow labels. Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to reevaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue. ### Issue team
- Looks for issues without workflow labels and triages issue
- Monitors pull requests
- Closes invalid issues and pull requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
- Assigns appropriate [labels](#how-we-handle-issues)
- Asks for feedback from issue reporter/pull request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
- Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](http://gitlab.org/team/)
- Monitors all issues/pull requests for feedback (but especially ones commented on since automatically watching them):
- Closes issues with no feedback from the reporter for two weeks
- Closes stale pull requests
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover. ### Development team
Priority (from high to low): - Responds to issues and pull requests the issue team mentions them in
- Monitors for new issues in _Awaiting developer action/feedback_ with no developer activity (once a week)
- Monitors for new pull requests (at least once a week)
- Manages their work queue by looking at issues and pull requests assigned to them
- Close fixed issues (via commit messages or manually)
- Codes [new features](http://feedback.gitlab.com/forums/176466-general/filters/top)!
- Response guidelines
- Be kind to people trying to contribute. Be aware that people can be a non-native or a native English speaker, they might not understand thing or they might be very sensitive to how your word things. Use emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to pull requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review).
## Priorities of the issue team
1. Mentioning people (critical)
2. Workflow labels (normal)
3. Functional labels (minor)
4. Assigning issues (avoid if possible)
1. Mentioning people (very important) ## Mentioning people
2. Workflow labels
3. Functional labels (less important) The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](http://gitlab.org/team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
4. Assigning issues (optional)
## Workflow labels ## Workflow labels
Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to reevaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue.
- _Awaiting feedback_: Feedback pending from the reporter - _Awaiting feedback_: Feedback pending from the reporter
- _Awaiting confirmation of fix_: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away) - _Awaiting confirmation of fix_: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away)
- _Attached PR_: There is a PR attached and the discussion should happen there - _Attached PR_: There is a PR attached and the discussion should happen there
...@@ -29,6 +53,10 @@ Priority (from high to low): ...@@ -29,6 +53,10 @@ Priority (from high to low):
These labels describe what development specialities are involved such as: PostgreSQL, UX, LDAP. These labels describe what development specialities are involved such as: PostgreSQL, UX, LDAP.
## Assigning issues
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
## Label colors ## Label colors
- Light orange `#fef2c0`: workflow labels for issue team members (awaiting feedback, awaiting confirmation of fix) - Light orange `#fef2c0`: workflow labels for issue team members (awaiting feedback, awaiting confirmation of fix)
- Bright orange `#eb6420`: workflow labels for core team members (attached PR, awaiting developer action/feedback) - Bright orange `#eb6420`: workflow labels for core team members (attached PR, awaiting developer action/feedback)
...@@ -37,32 +65,6 @@ These labels describe what development specialities are involved such as: Postgr ...@@ -37,32 +65,6 @@ These labels describe what development specialities are involved such as: Postgr
- Feature request (see copy & paste response: [Feature requests](#feature-requests)) - Feature request (see copy & paste response: [Feature requests](#feature-requests))
- Support (see copy & paste response: [Support requests and configuration questions](#support-requests-and-configuration-questions) - Support (see copy & paste response: [Support requests and configuration questions](#support-requests-and-configuration-questions)
## Common actions
### Issue team
- Looks for issues without workflow labels and triages issue
- Monitors pull requests
- Closes invalid issues and pull requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
- Assigns appropriate [labels](#how-we-handle-issues)
- Asks for feedback from issue reporter/pull request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
- Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](http://gitlab.org/team/)
- Monitors all issues/pull requests for feedback (but especially ones commented on since automatically watching them):
- Assigns issues to developers if they indicate they are fixing it
- Assigns pull requests to developers if they indicate they will take care of merge
- Closes issues with no feedback from the reporter for two weeks
- Closes stale pull requests
### Development team
- Responds to issues and pull requests the issue team mentions them in
- Monitors for new issues in _Awaiting developer action/feedback_ with no developer activity (once a week)
- Monitors for new pull requests (at least once a week)
- Manages their work queue by looking at issues and pull requests assigned to them
- Close fixed issues (via commit messages or manually)
- Codes [new features](http://feedback.gitlab.com/forums/176466-general/filters/top)!
- Response guidelines
- Be kind to people trying to contribute. Be aware that people can be a non-native or a native English speaker, they might not understand thing or they might be very sensitive to how your word things. Use emoji to express your feelings (hearth, star, smile, etc.). Some good tips about giving feedback to pull requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review).
## Copy & paste responses ## Copy & paste responses
### Improperly formatted issue ### Improperly formatted issue
......
...@@ -14,14 +14,12 @@ ...@@ -14,14 +14,12 @@
* powered by Ruby on Rails * powered by Ruby on Rails
* completely free and open source (MIT license) * completely free and open source (MIT license)
* used by more than 10.000 organizations to keep their code secure * used by more than 25.000 organizations to keep their code secure
### Code status ### Code status
* [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) * [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
* [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch)
* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) * [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
* [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available), gems are updated in major releases of GitLab. * [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available), gems are updated in major releases of GitLab.
...@@ -39,11 +37,10 @@ ...@@ -39,11 +37,10 @@
### Requirements ### Requirements
* Ubuntu/Debian** * Ubuntu/Debian**
* ruby 1.9.3 * ruby 1.9.3+
* git 1.7.10+
* redis 2.0+
* MySQL or PostgreSQL * MySQL or PostgreSQL
* git
* gitlab-shell
* redis
** More details are in the [requirements doc](doc/install/requirements.md) ** More details are in the [requirements doc](doc/install/requirements.md)
...@@ -80,7 +77,7 @@ Since 2011 GitLab is released on the 22nd of every month. Every new release incl ...@@ -80,7 +77,7 @@ Since 2011 GitLab is released on the 22nd of every month. Every new release incl
* [Changelog](CHANGELOG) * [Changelog](CHANGELOG)
* 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
......
class Activities
constructor: ->
Pager.init 20, true
$(".event_filter_link").bind "click", (event) =>
event.preventDefault()
@toggleFilter($(event.currentTarget))
@reloadActivities()
reloadActivities: ->
$(".content_list").html ''
Pager.init 20, true
toggleFilter: (sender) ->
sender.parent().toggleClass "inactive"
event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0]
if event_filters
event_filters = event_filters.split(",")
else
event_filters = new Array()
index = event_filters.indexOf(filter)
if index is -1
event_filters.push filter
else
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' }
@Activities = Activities
...@@ -29,4 +29,10 @@ class Admin ...@@ -29,4 +29,10 @@ class Admin
modal.hide() modal.hide()
$('.change-owner-link').show() $('.change-owner-link').show()
$('li.users_project').bind 'ajax:success', ->
Turbolinks.visit(location.href)
$('li.users_group').bind 'ajax:success', ->
Turbolinks.visit(location.href)
@Admin = Admin @Admin = Admin
class Dashboard class Dashboard
constructor: -> constructor: ->
Pager.init 20, true
@initSidebarTab() @initSidebarTab()
$(".event_filter_link").bind "click", (event) =>
event.preventDefault()
@toggleFilter($(event.currentTarget))
@reloadActivities()
$(".dash-filter").keyup -> $(".dash-filter").keyup ->
terms = $(this).val() terms = $(this).val()
uiBox = $(this).parents('.ui-box').first() uiBox = $(this).parents('.ui-box').first()
...@@ -24,27 +18,6 @@ class Dashboard ...@@ -24,27 +18,6 @@ class Dashboard
reloadActivities: ->
$(".content_list").html ''
Pager.init 20, true
toggleFilter: (sender) ->
sender.parent().toggleClass "inactive"
event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0]
if event_filters
event_filters = event_filters.split(",")
else
event_filters = new Array()
index = event_filters.indexOf(filter)
if index is -1
event_filters.push filter
else
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' }
initSidebarTab: -> initSidebarTab: ->
key = "dashboard_sidebar_filter" key = "dashboard_sidebar_filter"
......
...@@ -18,12 +18,15 @@ class Dispatcher ...@@ -18,12 +18,15 @@ class Dispatcher
switch page switch page
when 'projects:issues:index' when 'projects:issues:index'
Issues.init() Issues.init()
when 'projects:issues:new', 'projects:merge_requests:new'
GitLab.GfmAutoComplete.setup()
when 'dashboard:show' when 'dashboard:show'
new Dashboard() new Dashboard()
new Activities()
when 'projects:commit:show' when 'projects:commit:show'
new Commit() new Commit()
when 'groups:show', 'projects:show' when 'groups:show', 'projects:show'
Pager.init(20, true) new Activities()
when 'projects:new', 'projects:edit' when 'projects:new', 'projects:edit'
new Project() new Project()
when 'projects:walls:show' when 'projects:walls:show'
......
class Flash
constructor: (message, type)->
flash = $(".flash-container")
flash.html("")
$('<div/>',
class: "flash-#{type}",
text: message
).appendTo(".flash-container")
flash.click -> $(@).fadeOut()
flash.show()
setTimeout (-> flash.fadeOut()), 5000
@Flash = Flash
...@@ -44,7 +44,7 @@ GitLab.GfmAutoComplete = ...@@ -44,7 +44,7 @@ GitLab.GfmAutoComplete =
tpl: @Issues.template tpl: @Issues.template
callbacks: callbacks:
before_save: (issues) -> before_save: (issues) ->
$.map issues, (i) -> id: i.id, title: sanitize(i.title), search: "#{i.id} #{i.title}" $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
input.one "focus", => input.one "focus", =>
$.getJSON(@dataSource).done (data) -> $.getJSON(@dataSource).done (data) ->
......
...@@ -11,7 +11,7 @@ class MergeRequest ...@@ -11,7 +11,7 @@ class MergeRequest
constructor: (@opts) -> constructor: (@opts) ->
this.$el = $('.merge-request') this.$el = $('.merge-request')
@diffs_loaded = false @diffs_loaded = if @opts.action == 'diffs' then true else false
@commits_loaded = false @commits_loaded = false
this.activateTab(@opts.action) this.activateTab(@opts.action)
......
$ -> $ ->
$('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
# Hide any previous submission feedback
$('.edit_user .update-feedback').hide()
# Submit the form # Submit the form
$('.edit_user').submit() $('.edit_user').submit()
# Go up the hierarchy and show the corresponding submission feedback element new Flash("Appearance settings saved", "notice")
$(@).closest('fieldset').find('.update-feedback').show('highlight', {color: '#DFF0D8'}, 500)
$('.update-username form').on 'ajax:before', -> $('.update-username form').on 'ajax:before', ->
$('.loading-gif').show() $('.loading-gif').show()
......
...@@ -35,6 +35,7 @@ $ -> ...@@ -35,6 +35,7 @@ $ ->
$('a, button', scope).removeClass 'active' $('a, button', scope).removeClass 'active'
$(@).addClass 'active' $(@).addClass 'active'
$('#project_clone', scope).val $(@).data 'clone' $('#project_clone', scope).val $(@).data 'clone'
$(".clone").text("").append 'git remote add origin ' + $(@).data 'clone'
# Ref switcher # Ref switcher
$('.project-refs-select').on 'change', -> $('.project-refs-select').on 'change', ->
......
...@@ -55,6 +55,7 @@ table a code { ...@@ -55,6 +55,7 @@ table a code {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
opacity: 0.8; opacity: 0.8;
z-index: 100;
.flash-notice { .flash-notice {
background: #49C; background: #49C;
...@@ -376,3 +377,12 @@ table { ...@@ -376,3 +377,12 @@ table {
text-align: center; text-align: center;
} }
} }
.dashboard-intro-icon {
float: left;
font-size: 32px;
color: #AAA;
padding: 5px 0;
width: 50px;
min-height: 100px;
}
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.hint { font-style: italic; color: #999; } .hint { font-style: italic; color: #999; }
.light { color: #888 } .light { color: #888 }
.tiny { font-weight: normal } .tiny { font-weight: normal }
.vtop { vertical-align: top; } .vtop { vertical-align: top !important; }
/** ALERT MESSAGES **/ /** ALERT MESSAGES **/
...@@ -56,23 +56,6 @@ ...@@ -56,23 +56,6 @@
line-height: 24px; line-height: 24px;
} }
/** FORMS **/
input[type='search'].search-text-input {
background-image: url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
padding-left: 25px;
@include border-radius(4px);
border: 1px solid #ccc;
}
input[type='text'].danger {
background: #F2DEDE!important;
border-color: #D66;
text-shadow: 0 1px 1px #fff
}
fieldset legend { font-size: 15px; }
.tab-content { .tab-content {
overflow: visible; overflow: visible;
......
...@@ -6,10 +6,9 @@ form { ...@@ -6,10 +6,9 @@ form {
} }
} }
input { input.input-xpadding,
&.input-xpadding { .add-on.input-xpadding {
padding: 6px 10px; padding: 6px 10px;
}
} }
.control-group { .control-group {
...@@ -31,3 +30,22 @@ input { ...@@ -31,3 +30,22 @@ input {
} }
} }
input[type='search'].search-text-input {
background-image: url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
padding-left: 25px;
@include border-radius(4px);
border: 1px solid #ccc;
}
input[type='text'].danger {
background: #F2DEDE!important;
border-color: #D66;
text-shadow: 0 1px 1px #fff
}
fieldset legend {
font-size: 16px;
margin-bottom: 10px;
}
...@@ -89,6 +89,12 @@ ...@@ -89,6 +89,12 @@
h2 { margin-top: 25px;} h2 { margin-top: 25px;}
h3 { margin-top: 20px;} h3 { margin-top: 20px;}
h4 { margin-top: 15px;} h4 { margin-top: 15px;}
blockquote p {
color: #888;
font-size: 14px;
line-height: 1.5;
}
} }
@mixin page-title { @mixin page-title {
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
background: #F1F1F1; background: #F1F1F1;
color: $style_color; color: $style_color;
font-weight: bold; font-weight: bold;
text-shadow: 0 1px 1px #fff;
} }
&.nav-stacked-menu { &.nav-stacked-menu {
......
...@@ -345,37 +345,6 @@ ...@@ -345,37 +345,6 @@
} }
/**
* COMMIT ROW
*/
.commit {
.browse_code_link_holder {
float: right;
}
.committed_ago {
float: right;
@extend .cgray;
}
.notes_count {
float: right;
margin-right: 10px;
}
code {
background: #FCEEC1;
color: $style_color;
}
.commit_short_id {
float: left;
@extend .lined;
min-width: 65px;
font-family: $monospace_font;
}
}
.file-stats a { .file-stats a {
color: $style_color; color: $style_color;
} }
...@@ -463,24 +432,6 @@ ...@@ -463,24 +432,6 @@
line-height: 2; line-height: 2;
} }
li.commit {
.avatar {
width: 24px;
top:-5px;
margin-right: 5px;
margin-left: 10px;
}
code {
padding: 2px 2px 0;
margin-top: -2px;
&:hover {
color: black;
border: 1px solid #ccc;
}
}
}
.commit-breadcrumb { .commit-breadcrumb {
padding: 0; padding: 0;
} }
...@@ -496,3 +447,61 @@ li.commit { ...@@ -496,3 +447,61 @@ li.commit {
font-weight: bold; font-weight: bold;
} }
} }
.lists-separator {
margin: 10px 0;
border-top: 1px dashed #CCC;
}
/**
* COMMIT ROW
*/
li.commit {
padding: 8px;
.commit-row-title {
font-size: 14px;
margin-bottom: 2px;
.notes_count {
float: right;
margin-right: 10px;
}
.commit_short_id {
min-width: 65px;
font-family: $monospace_font;
}
.commit-row-message {
color: #555;
font-weight: bolder;
&:hover {
color: #444;
text-decoration: underline;
}
}
}
.commit-row-info {
a {
color: #777;
}
.committed_ago {
float: right;
@extend .cgray;
}
}
&.inline-commit {
.commit-row-title {
font-size: 13px;
}
.committed_ago {
float: right;
@extend .cgray;
}
}
}
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
} }
.project-row, .group-row { .project-row, .group-row {
padding: 12px 15px !important; padding: 10px 15px !important;
.namespace-name { .namespace-name {
color: #666; color: #666;
...@@ -77,7 +77,6 @@ ...@@ -77,7 +77,6 @@
font-size: 16px; font-size: 16px;
} }
.arrow { .arrow {
float: right; float: right;
padding: 10px 5px; padding: 10px 5px;
......
...@@ -56,10 +56,6 @@ ...@@ -56,10 +56,6 @@
margin-left: 35px; margin-left: 35px;
margin-right: 100px; margin-right: 100px;
.commit p {
color: #666;
padding-top: 5px;
}
.event-info { .event-info {
color: #666; color: #666;
} }
...@@ -80,6 +76,10 @@ ...@@ -80,6 +76,10 @@
margin-left: 0px; margin-left: 0px;
max-width: 200px; max-width: 200px;
} }
p:last-child {
margin-bottom: 0;
}
} }
.event-note-icon { .event-note-icon {
color: #777; color: #777;
...@@ -103,13 +103,6 @@ ...@@ -103,13 +103,6 @@
} }
} }
ul {
.avatar {
width: 18px;
margin: 2px 4px;
}
}
&:last-child { border:none } &:last-child { border:none }
.event_commits { .event_commits {
...@@ -120,12 +113,14 @@ ...@@ -120,12 +113,14 @@
background: transparent; background: transparent;
padding: 3px; padding: 3px;
border: none; border: none;
color: #666;
.commit-row-title {
font-size: 12px; font-size: 12px;
} }
}
&.commits-stat { &.commits-stat {
display: block; display: block;
padding: 3px; padding: 3px;
margin-top: 3px;
&:hover { &:hover {
background: none; background: none;
......
...@@ -64,13 +64,6 @@ ...@@ -64,13 +64,6 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
padding: 5px 0; padding: 5px 0;
.avatar { position:relative }
.commit-author-name,
.dash,
.committed_ago,
.browse_code_link_holder {
display: none;
}
list-style: none; list-style: none;
&:hover { &:hover {
background: none; background: none;
...@@ -117,7 +110,7 @@ ...@@ -117,7 +110,7 @@
.merge-request-angle { .merge-request-angle {
text-align: center; text-align: center;
margin-top: 45px; margin: 0;
} }
.merge-request-form-info { .merge-request-form-info {
......
.save-status-fixed {
position: fixed;
left: 20px;
bottom: 50px;
}
.update-notifications { .update-notifications {
margin-bottom: 0; margin-bottom: 0;
label { label {
......
...@@ -36,6 +36,12 @@ ...@@ -36,6 +36,12 @@
} }
} }
.project-public-holder {
.help-inline {
padding-top: 7px;
}
}
.save-project-loader { .save-project-loader {
img { img {
margin-top: 50px; margin-top: 50px;
......
.application-theme, .code-preview-theme {
.update-feedback {
color: #468847;
float: right;
}
}
.themes_opts { .themes_opts {
padding-left: 20px; padding-left: 20px;
......
...@@ -15,6 +15,12 @@ ...@@ -15,6 +15,12 @@
.tree-table { .tree-table {
@include border-radius(0); @include border-radius(0);
.tree-item { .tree-item {
td {
padding: 8px 10px;
strong {
font-weight: normal;
}
}
&:hover { &:hover {
td { td {
background: $hover; background: $hover;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
&.navbar-gitlab { &.navbar-gitlab {
.navbar-inner { .navbar-inner {
background: #547; background: #547;
border-bottom: 1px solid #435;
.app_logo { .app_logo {
&:hover { &:hover {
background-color: #435; background-color: #435;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
&.navbar-gitlab { &.navbar-gitlab {
.navbar-inner { .navbar-inner {
background: #373737; background: #373737;
border-bottom: 1px solid #272727;
.app_logo { .app_logo {
&:hover { &:hover {
background-color: #272727; background-color: #272727;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
&.navbar-gitlab { &.navbar-gitlab {
.navbar-inner { .navbar-inner {
background: #345; background: #345;
border-bottom: 1px solid #234;
.app_logo { .app_logo {
&:hover { &:hover {
background-color: #234; background-color: #234;
......
...@@ -20,7 +20,8 @@ class CommitLoadContext < BaseContext ...@@ -20,7 +20,8 @@ class CommitLoadContext < BaseContext
result[:notes_count] = project.notes.for_commit_id(commit.id).count result[:notes_count] = project.notes.for_commit_id(commit.id).count
begin begin
result[:suppress_diff] = true if commit.diffs.size > Commit::DIFF_SAFE_SIZE && !params[:force_show_diff] result[:suppress_diff] = true if commit.diff_suppress? && !params[:force_show_diff]
result[:force_suppress_diff] = commit.diff_force_suppress?
rescue Grit::Git::GitTimeout rescue Grit::Git::GitTimeout
result[:suppress_diff] = true result[:suppress_diff] = true
result[:status] = :huge_commit result[:status] = :huge_commit
......
...@@ -16,8 +16,9 @@ module Projects ...@@ -16,8 +16,9 @@ module Projects
wiki_enabled: default_features.wiki, wiki_enabled: default_features.wiki,
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
}.stringify_keys
@project = Project.new(default_opts.merge(params)) @project = Project.new(default_opts.merge(params))
......
class Admin::MembersController < Admin::ApplicationController
def destroy
user = User.find_by_username(params[:id])
project = Project.find_with_namespace(params[:project_id])
project.users_projects.where(user_id: user).first.destroy
redirect_to :back
end
end
...@@ -13,7 +13,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -13,7 +13,7 @@ class Admin::UsersController < Admin::ApplicationController
end end
def new def new
@user = User.new.with_defaults @user = User.build_user
end end
def edit def edit
...@@ -44,7 +44,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -44,7 +44,7 @@ class Admin::UsersController < Admin::ApplicationController
password_expires_at: Time.now password_expires_at: Time.now
} }
@user = User.new(params[:user].merge(opts), as: :admin) @user = User.build_user(params[:user].merge(opts), as: :admin)
@user.admin = (admin && admin.to_i > 0) @user.admin = (admin && admin.to_i > 0)
@user.created_by_id = current_user.id @user.created_by_id = current_user.id
...@@ -83,9 +83,10 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -83,9 +83,10 @@ class Admin::UsersController < Admin::ApplicationController
end end
def destroy def destroy
if user.personal_projects.count > 0 # 1. Remove groups where user is the only owner
redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return user.solo_owned_groups.map(&:destroy)
end
# 2. Remove user with all authored content including personal projects
user.destroy user.destroy
respond_to do |format| respond_to do |format|
......
...@@ -96,10 +96,6 @@ class ApplicationController < ActionController::Base ...@@ -96,10 +96,6 @@ class ApplicationController < ActionController::Base
return access_denied! unless can?(current_user, :push_code, project) return access_denied! unless can?(current_user, :push_code, project)
end end
def authorize_create_team!
return access_denied! unless can?(current_user, :create_team, nil)
end
def access_denied! def access_denied!
render "errors/access_denied", layout: "errors", status: 404 render "errors/access_denied", layout: "errors", status: 404
end end
...@@ -156,7 +152,7 @@ class ApplicationController < ActionController::Base ...@@ -156,7 +152,7 @@ class ApplicationController < ActionController::Base
end end
def check_password_expiration def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
redirect_to new_profile_password_path and return redirect_to new_profile_password_path and return
end end
end end
...@@ -169,4 +165,9 @@ class ApplicationController < ActionController::Base ...@@ -169,4 +165,9 @@ class ApplicationController < ActionController::Base
current_user.save current_user.save
end end
end end
def event_filter
filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters)
end
end end
...@@ -33,9 +33,13 @@ class DashboardController < ApplicationController ...@@ -33,9 +33,13 @@ class DashboardController < ApplicationController
current_user.owned_projects current_user.owned_projects
else else
current_user.authorized_projects current_user.authorized_projects
end.sorted_by_activity end
@projects = @projects.where(namespace_id: Group.find_by_name(params[:group])) if params[:group].present?
@projects = @projects.includes(:namespace).sorted_by_activity
@labels = current_user.authorized_projects.tags_on(:labels) @labels = current_user.authorized_projects.tags_on(:labels)
@groups = current_user.authorized_groups
@projects = @projects.tagged_with(params[:label]) if params[:label].present? @projects = @projects.tagged_with(params[:label]) if params[:label].present?
@projects = @projects.page(params[:page]).per(30) @projects = @projects.page(params[:page]).per(30)
...@@ -66,9 +70,4 @@ class DashboardController < ApplicationController ...@@ -66,9 +70,4 @@ class DashboardController < ApplicationController
def load_projects def load_projects
@projects = current_user.authorized_projects.sorted_by_activity @projects = current_user.authorized_projects.sorted_by_activity
end end
def event_filter
filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters)
end
end end
...@@ -31,7 +31,9 @@ class GroupsController < ApplicationController ...@@ -31,7 +31,9 @@ class GroupsController < ApplicationController
end end
def show def show
@events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) @events = Event.in_projects(project_ids)
@events = event_filter.apply_filter(@events)
@events = @events.limit(20).offset(params[:offset] || 0)
@last_push = current_user.recent_push @last_push = current_user.recent_push
@shared_projects = @group.shared_projects @shared_projects = @group.shared_projects
...@@ -110,7 +112,7 @@ class GroupsController < ApplicationController ...@@ -110,7 +112,7 @@ class GroupsController < ApplicationController
# Dont allow unauthorized access to group # Dont allow unauthorized access to group
def authorize_read_group! def authorize_read_group!
unless projects.present? or can?(current_user, :manage_group, @group) unless projects.present? or can?(current_user, :read_group, @group)
return render_404 return render_404
end end
end end
......
...@@ -16,38 +16,42 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -16,38 +16,42 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
def ldap def ldap
# We only find ourselves here if the authentication to LDAP was successful. # We only find ourselves here
@user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user) # if the authentication to LDAP was successful.
if @user.persisted? @user = Gitlab::LDAP::User.find_or_create(oauth)
@user.remember_me = true @user.remember_me = true if @user.persisted?
end
Gitlab::LDAP::Access.new.update_permissions(@user) Gitlab::LDAP::Access.new.update_permissions(@user)
sign_in_and_redirect(@user)
sign_in_and_redirect @user
end end
private private
def handle_omniauth def handle_omniauth
oauth = request.env['omniauth.auth']
provider, uid = oauth['provider'], oauth['uid']
if current_user if current_user
# Change a logged-in user's authentication method: # Change a logged-in user's authentication method:
current_user.extern_uid = uid current_user.extern_uid = oauth['uid']
current_user.provider = provider current_user.provider = oauth['provider']
current_user.save current_user.save
redirect_to profile_path redirect_to profile_path
else else
@user = User.find_or_new_for_omniauth(oauth) @user = Gitlab::OAuth::User.find(oauth)
# Create user if does not exist
# and allow_single_sign_on is true
if Gitlab.config.omniauth['allow_single_sign_on']
@user ||= Gitlab::OAuth::User.create(oauth)
end
if @user if @user
sign_in_and_redirect @user sign_in_and_redirect(@user)
else else
flash[:notice] = "There's no such user!" flash[:notice] = "There's no such user!"
redirect_to new_user_session_path redirect_to new_user_session_path
end end
end end
end end
def oauth
@oauth ||= request.env['omniauth.auth']
end
end end
...@@ -18,6 +18,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -18,6 +18,7 @@ class Projects::CommitController < Projects::ApplicationController
end end
@suppress_diff = result[:suppress_diff] @suppress_diff = result[:suppress_diff]
@force_suppress_diff = result[:force_suppress_diff]
@note = result[:note] @note = result[:note]
@line_notes = result[:line_notes] @line_notes = result[:line_notes]
......
...@@ -8,13 +8,17 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -8,13 +8,17 @@ class Projects::CompareController < Projects::ApplicationController
end end
def show def show
compare = Gitlab::Git::Compare.new(project.repository, params[:from], params[:to]) compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to])
@commits = compare.commits @commits = compare.commits
@commit = compare.commit @commit = compare.commit
@diffs = compare.diffs @diffs = compare.diffs
@refs_are_same = compare.same @refs_are_same = compare.same
@line_notes = [] @line_notes = []
diff_line_count = Commit::diff_line_count(@diffs)
@suppress_diff = Commit::diff_suppress?(@diffs, diff_line_count) && !params[:force_show_diff]
@force_suppress_diff = Commit::diff_force_suppress?(@diffs, diff_line_count)
end end
def create def create
......
...@@ -91,7 +91,11 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -91,7 +91,11 @@ class Projects::IssuesController < Projects::ApplicationController
protected protected
def issue def issue
@issue ||= @project.issues.find(params[:id]) @issue ||= begin
@project.issues.find_by_iid!(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_old
end
end end
def authorize_modify_issue! def authorize_modify_issue!
...@@ -109,4 +113,20 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -109,4 +113,20 @@ class Projects::IssuesController < Projects::ApplicationController
def issues_filtered def issues_filtered
@issues = Issues::ListContext.new(project, current_user, params).execute @issues = Issues::ListContext.new(project, current_user, params).execute
end end
# Since iids are implemented only in 6.1
# user may navigate to issue page using old global ids.
#
# To prevent 404 errors we provide a redirect to correct iids until 7.0 release
#
def redirect_old
issue = @project.issues.find_by_id(params[:id])
if issue
redirect_to project_issue_path(@project, issue)
return
else
raise ActiveRecord::RecordNotFound.new
end
end
end end
...@@ -3,6 +3,7 @@ require 'gitlab/satellite/satellite' ...@@ -3,6 +3,7 @@ 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, :commits, :diffs, :automerge, :automerge_check, :ci_status]
before_filter :closes_issues, only: [:edit, :update, :show, :commits, :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]
...@@ -39,6 +40,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -39,6 +40,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@comments_target = {noteable_type: 'MergeRequest', @comments_target = {noteable_type: 'MergeRequest',
noteable_id: @merge_request.id} noteable_id: @merge_request.id}
@line_notes = @merge_request.notes.where("line_code is not null") @line_notes = @merge_request.notes.where("line_code is not null")
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]
@force_suppress_diff = Commit::diff_force_suppress?(@merge_request.diffs, diff_line_count)
end end
def new def new
...@@ -132,7 +137,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -132,7 +137,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def merge_request def merge_request
@merge_request ||= @project.merge_requests.find(params[:id]) @merge_request ||= @project.merge_requests.find_by_iid!(params[:id])
end
def closes_issues
@closes_issues ||= @merge_request.closes_issues
end end
def authorize_modify_merge_request! def authorize_modify_merge_request!
......
...@@ -81,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -81,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController
protected protected
def milestone def milestone
@milestone ||= @project.milestones.find(params[:id]) @milestone ||= @project.milestones.find_by_iid!(params[:id])
end end
def authorize_admin_milestone! def authorize_admin_milestone!
......
...@@ -8,10 +8,6 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -8,10 +8,6 @@ class Projects::NetworkController < Projects::ApplicationController
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
if @options[:q]
@commit = @project.repository.commit(@options[:q]) || @commit
end
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -11,9 +11,17 @@ class Projects::RawController < Projects::ApplicationController ...@@ -11,9 +11,17 @@ class Projects::RawController < Projects::ApplicationController
@blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path) @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path)
if @blob.exists? if @blob.exists?
type = if @blob.mime_type =~ /html|javascript/
'text/plain; charset=utf-8'
else
@blob.mime_type
end
headers['X-Content-Type-Options'] = 'nosniff'
send_data( send_data(
@blob.data, @blob.data,
type: @blob.mime_type, type: type,
disposition: 'inline', disposition: 'inline',
filename: @blob.name filename: @blob.name
) )
......
...@@ -55,7 +55,10 @@ class ProjectsController < Projects::ApplicationController ...@@ -55,7 +55,10 @@ class ProjectsController < Projects::ApplicationController
def show def show
limit = (params[:limit] || 20).to_i limit = (params[:limit] || 20).to_i
@events = @project.events.recent.limit(limit).offset(params[:offset] || 0)
@events = @project.events.recent
@events = event_filter.apply_filter(@events)
@events = @events.limit(limit).offset(params[:offset] || 0)
# Ensure project default branch is set if it possible # Ensure project default branch is set if it possible
# Normally it defined on push or during creation # Normally it defined on push or during creation
...@@ -104,7 +107,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -104,7 +107,7 @@ class ProjectsController < Projects::ApplicationController
def autocomplete_sources def autocomplete_sources
@suggestions = { @suggestions = {
emojis: Emoji.names, emojis: Emoji.names,
issues: @project.issues.select([:id, :title, :description]), issues: @project.issues.select([:iid, :title, :description]),
members: @project.team.members.sort_by(&:username).map { |user| { username: user.username, name: user.name } } members: @project.team.members.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
} }
......
...@@ -2,9 +2,6 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -2,9 +2,6 @@ class RegistrationsController < Devise::RegistrationsController
before_filter :signup_enabled? before_filter :signup_enabled?
def destroy def destroy
if current_user.owned_projects.count > 0
redirect_to account_profile_path, alert: "Remove projects and groups before removing account." and return
end
current_user.destroy current_user.destroy
respond_to do |format| respond_to do |format|
......
...@@ -235,4 +235,11 @@ module ApplicationHelper ...@@ -235,4 +235,11 @@ module ApplicationHelper
"Search" "Search"
end end
end end
def first_line(str)
lines = str.split("\n")
line = lines.first
line += "..." if lines.size > 1
line
end
end end
...@@ -56,8 +56,9 @@ module CommitsHelper ...@@ -56,8 +56,9 @@ module CommitsHelper
end end
end end
def commit_to_html commit, project def commit_to_html(commit, project, inline = true)
escape_javascript(render 'projects/commits/commit', commit: commit, project: project) unless commit.nil? template = inline ? "inline_commit" : "commit"
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
end end
def diff_line_content(line) def diff_line_content(line)
......
module CompareHelper
def compare_to_mr_button?
params[:from].present? && params[:to].present? &&
@repository.branch_names.include?(params[:from]) &&
@repository.branch_names.include?(params[:to]) &&
params[:from] != params[:to] &&
!@refs_are_same
end
def compare_mr_path
new_project_merge_request_path(@project, merge_request: {source_branch: params[:to], target_branch: params[:from]})
end
end
...@@ -28,7 +28,7 @@ module EventsHelper ...@@ -28,7 +28,7 @@ module EventsHelper
end end
content_tag :div, class: "filter_icon #{inactive}" do content_tag :div, class: "filter_icon #{inactive}" do
link_to dashboard_path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
content_tag :i, nil, class: icon_for_event[key] content_tag :i, nil, class: icon_for_event[key]
end end
end end
...@@ -109,7 +109,7 @@ module EventsHelper ...@@ -109,7 +109,7 @@ module EventsHelper
else else
link_to event_note_target_path(event) do link_to event_note_target_path(event) do
content_tag :strong do content_tag :strong do
"#{event.note_target_type} ##{truncate event.note_target_id}" "#{event.note_target_type} ##{truncate event.note_target_iid}"
end end
end end
end end
...@@ -123,6 +123,8 @@ module EventsHelper ...@@ -123,6 +123,8 @@ module EventsHelper
end end
def event_note(text) def event_note(text)
sanitize(markdown(truncate(text, length: 150)), tags: %w(a img b pre p)) text = first_line(text)
text = truncate(text, length: 150)
sanitize(markdown(text), tags: %w(a img b pre p))
end end
end end
module GraphHelper module GraphHelper
def get_refs(commit) def get_refs(repo, commit)
refs = "" refs = ""
refs += commit.refs.collect{|r|r.name}.join(" ") if commit.refs refs += commit.ref_names(repo).join(" ")
# append note count # append note count
refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
......
...@@ -37,23 +37,23 @@ module IssuesHelper ...@@ -37,23 +37,23 @@ module IssuesHelper
end end
end end
def url_for_issue(issue_id) def url_for_issue(issue_iid)
return "" if @project.nil? return "" if @project.nil?
if @project.used_default_issues_tracker? if @project.used_default_issues_tracker?
url = project_issue_url project_id: @project, id: issue_id url = project_issue_url project_id: @project, id: issue_iid
else else
url = Gitlab.config.issues_tracker[@project.issues_tracker]["issues_url"] url = Gitlab.config.issues_tracker[@project.issues_tracker]["issues_url"]
url.gsub(':id', issue_id.to_s) url.gsub(':id', issue_iid.to_s)
.gsub(':project_id', @project.id.to_s) .gsub(':project_id', @project.id.to_s)
.gsub(':issues_tracker_id', @project.issues_tracker_id.to_s) .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
end end
end end
def title_for_issue(issue_id) def title_for_issue(issue_iid)
return "" if @project.nil? return "" if @project.nil?
if @project.used_default_issues_tracker? && issue = @project.issues.where(id: issue_id).first if @project.used_default_issues_tracker? && issue = @project.issues.where(iid: issue_iid).first
issue.title issue.title
else else
"" ""
......
...@@ -4,4 +4,16 @@ module ProfileHelper ...@@ -4,4 +4,16 @@ module ProfileHelper
'active' 'active'
end end
end end
def show_profile_username_tab?
current_user.can_change_username?
end
def show_profile_social_tab?
Gitlab.config.omniauth.enabled && !current_user.ldap_user?
end
def show_profile_remove_tab?
Gitlab.config.gitlab.signup_enabled && !current_user.ldap_user?
end
end end
module Emails
module Groups
def group_access_granted_email(user_group_id)
@membership = UsersGroup.find(user_group_id)
@group = @membership.group
mail(to: @membership.user.email,
subject: subject("access to group was granted"))
end
end
end
...@@ -3,14 +3,14 @@ module Emails ...@@ -3,14 +3,14 @@ module Emails
def new_issue_email(recipient_id, issue_id) def new_issue_email(recipient_id, issue_id)
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@project = @issue.project @project = @issue.project
mail(to: recipient(recipient_id), subject: subject("new issue ##{@issue.id}", @issue.title)) mail(to: recipient(recipient_id), subject: subject("new issue ##{@issue.iid}", @issue.title))
end end
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
@issue = Issue.find(issue_id) @issue = Issue.find(issue_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 = @issue.project @project = @issue.project
mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title)) mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.iid}", @issue.title))
end end
def closed_issue_email(recipient_id, issue_id, updated_by_user_id) def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
...@@ -18,7 +18,7 @@ module Emails ...@@ -18,7 +18,7 @@ module Emails
@project = @issue.project @project = @issue.project
@updated_by = User.find updated_by_user_id @updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id), mail(to: recipient(recipient_id),
subject: subject("Closed issue ##{@issue.id}", @issue.title)) subject: subject("Closed issue ##{@issue.iid}", @issue.title))
end end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
...@@ -27,7 +27,7 @@ module Emails ...@@ -27,7 +27,7 @@ module Emails
@project = @issue.project @project = @issue.project
@updated_by = User.find updated_by_user_id @updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id), mail(to: recipient(recipient_id),
subject: subject("changed issue ##{@issue.id}", @issue.title)) subject: subject("changed issue ##{@issue.iid}", @issue.title))
end end
end end
end end
...@@ -2,24 +2,24 @@ module Emails ...@@ -2,24 +2,24 @@ 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)
mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.id}", @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
mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @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
mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @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)
mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.id}", @merge_request.title)) mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.iid}", @merge_request.title))
end end
end end
......
...@@ -11,14 +11,14 @@ module Emails ...@@ -11,14 +11,14 @@ module Emails
@note = Note.find(note_id) @note = Note.find(note_id)
@issue = @note.noteable @issue = @note.noteable
@project = @note.project @project = @note.project
mail(to: recipient(recipient_id), subject: subject("note for issue ##{@issue.id}")) mail(to: recipient(recipient_id), subject: subject("note for issue ##{@issue.iid}"))
end end
def note_merge_request_email(recipient_id, note_id) def note_merge_request_email(recipient_id, note_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@merge_request = @note.noteable @merge_request = @note.noteable
@project = @note.project @project = @note.project
mail(to: recipient(recipient_id), subject: subject("note for merge request !#{@merge_request.id}")) mail(to: recipient(recipient_id), subject: subject("note for merge request !#{@merge_request.iid}"))
end end
def note_wall_email(recipient_id, note_id) def note_wall_email(recipient_id, note_id)
......
module Emails
module Profile
def new_user_email(user_id, password)
@user = User.find(user_id)
@password = password
mail(to: @user.email, subject: subject("Account was created for you"))
end
def new_ssh_key_email(key_id)
@key = Key.find(key_id)
@user = @key.user
mail(to: @user.email, subject: subject("SSH key was added to your account"))
end
end
end
...@@ -7,7 +7,6 @@ module Emails ...@@ -7,7 +7,6 @@ module Emails
subject: subject("access to project was granted")) subject: subject("access to project was granted"))
end end
def project_was_moved_email(project_id, user_id) def project_was_moved_email(project_id, user_id)
@user = User.find user_id @user = User.find user_id
@project = Project.find project_id @project = Project.find project_id
......
...@@ -3,6 +3,8 @@ class Notify < ActionMailer::Base ...@@ -3,6 +3,8 @@ class Notify < ActionMailer::Base
include Emails::MergeRequests include Emails::MergeRequests
include Emails::Notes include Emails::Notes
include Emails::Projects include Emails::Projects
include Emails::Profile
include Emails::Groups
add_template_helper ApplicationHelper add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper add_template_helper GitlabMarkdownHelper
...@@ -20,18 +22,6 @@ class Notify < ActionMailer::Base ...@@ -20,18 +22,6 @@ class Notify < ActionMailer::Base
delay_for(2.seconds) delay_for(2.seconds)
end end
def new_user_email(user_id, password)
@user = User.find(user_id)
@password = password
mail(to: @user.email, subject: subject("Account was created for you"))
end
def new_ssh_key_email(key_id)
@key = Key.find(key_id)
@user = @key.user
mail(to: @user.email, subject: subject("SSH key was added to your account"))
end
private private
# Look up a User by their ID and return their email address # Look up a User by their ID and return their email address
......
...@@ -2,6 +2,7 @@ class Ability ...@@ -2,6 +2,7 @@ class Ability
class << self class << self
def allowed(user, subject) def allowed(user, subject)
return [] unless user.kind_of?(User) return [] unless user.kind_of?(User)
return [] if user.blocked?
case subject.class.name case subject.class.name
when "Project" then project_abilities(user, subject) when "Project" then project_abilities(user, subject)
...@@ -134,6 +135,10 @@ class Ability ...@@ -134,6 +135,10 @@ class Ability
def group_abilities user, group def group_abilities user, group
rules = [] rules = []
if group.users.include?(user)
rules << :read_group
end
# Only group owner and administrators can manage group # Only group owner and administrators can manage group
if group.owners.include?(user) || user.admin? if group.owners.include?(user) || user.admin?
rules << [ rules << [
......
...@@ -2,16 +2,45 @@ class Commit ...@@ -2,16 +2,45 @@ class Commit
include ActiveModel::Conversion include ActiveModel::Conversion
include StaticModel include StaticModel
extend ActiveModel::Naming extend ActiveModel::Naming
include Mentionable
# Safe amount of files with diffs in one commit to render attr_mentionable :safe_message
# Safe amount of changes (files and lines) in one commit to render
# Used to prevent 500 error on huge commits by suppressing diff # Used to prevent 500 error on huge commits by suppressing diff
# #
DIFF_SAFE_SIZE = 100 # User can force display of diff above this size
DIFF_SAFE_FILES = 100
DIFF_SAFE_LINES = 5000
# Commits above this size will not be rendered in HTML
DIFF_HARD_LIMIT_FILES = 500
DIFF_HARD_LIMIT_LINES = 10000
def self.decorate(commits) def self.decorate(commits)
commits.map { |c| self.new(c) } commits.map { |c| self.new(c) }
end end
# Calculate number of lines to render for diffs
def self.diff_line_count(diffs)
diffs.reduce(0){|sum, d| sum + d.diff.lines.count}
end
def self.diff_suppress?(diffs, line_count = nil)
# optimize - check file count first
return true if diffs.size > DIFF_SAFE_FILES
line_count ||= Commit::diff_line_count(diffs)
line_count > DIFF_SAFE_LINES
end
def self.diff_force_suppress?(diffs, line_count = nil)
# optimize - check file count first
return true if diffs.size > DIFF_HARD_LIMIT_FILES
line_count ||= Commit::diff_line_count(diffs)
line_count > DIFF_HARD_LIMIT_LINES
end
attr_accessor :raw attr_accessor :raw
def initialize(raw_commit) def initialize(raw_commit)
...@@ -24,6 +53,19 @@ class Commit ...@@ -24,6 +53,19 @@ class Commit
@raw.id @raw.id
end end
def diff_line_count
@diff_line_count ||= Commit::diff_line_count(self.diffs)
@diff_line_count
end
def diff_suppress?
Commit::diff_suppress?(self.diffs, diff_line_count)
end
def diff_force_suppress?
Commit::diff_force_suppress?(self.diffs, diff_line_count)
end
# Returns a string describing the commit for use in a link title # Returns a string describing the commit for use in a link title
# #
# Example # Example
...@@ -65,6 +107,29 @@ class Commit ...@@ -65,6 +107,29 @@ class Commit
end end
end end
# Regular expression that identifies commit message clauses that trigger issue closing.
def issue_closing_regex
@issue_closing_regex ||= Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
end
# Discover issues should be closed when this commit is pushed to a project's
# default branch.
def closes_issues project
md = issue_closing_regex.match(safe_message)
if md
extractor = Gitlab::ReferenceExtractor.new
extractor.analyze(md[0])
extractor.issues_for(project)
else
[]
end
end
# Mentionable override.
def gfm_reference
"commit #{sha[0..5]}"
end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
@raw.send(m, *args, &block) @raw.send(m, *args, &block)
end end
......
module InternalId
extend ActiveSupport::Concern
included do
validate :set_iid, on: :create
validates :iid, presence: true, numericality: true
end
def set_iid
max_iid = project.send(self.class.name.tableize).maximum(:iid)
self.iid = max_iid.to_i + 1
end
def to_param
iid.to_s
end
end
...@@ -24,6 +24,7 @@ module Issuable ...@@ -24,6 +24,7 @@ module Issuable
scope :unassigned, -> { where("assignee_id IS NULL") } scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
delegate :name, delegate :name,
:email, :email,
to: :author, to: :author,
...@@ -36,6 +37,8 @@ module Issuable ...@@ -36,6 +37,8 @@ module Issuable
prefix: true prefix: true
attr_accessor :author_id_of_changes attr_accessor :author_id_of_changes
attr_mentionable :title, :description
end end
module ClassMethods module ClassMethods
......
# == Mentionable concern # == Mentionable concern
# #
# Contains common functionality shared between Issues and Notes # Contains functionality related to objects that can mention Users, Issues, MergeRequests, or Commits by
# GFM references.
# #
# Used by Issue, Note # Used by Issue, Note, MergeRequest, and Commit.
# #
module Mentionable module Mentionable
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods
# Indicate which attributes of the Mentionable to search for GFM references.
def attr_mentionable *attrs
mentionable_attrs.concat(attrs.map(&:to_s))
end
# Accessor for attributes marked mentionable.
def mentionable_attrs
@mentionable_attrs ||= []
end
end
# Generate a GFM back-reference that will construct a link back to this Mentionable when rendered. Must
# be overridden if this model object can be referenced directly by GFM notation.
def gfm_reference
raise NotImplementedError.new("#{self.class} does not implement #gfm_reference")
end
# Construct a String that contains possible GFM references.
def mentionable_text
self.class.mentionable_attrs.map { |attr| send(attr) || '' }.join
end
# The GFM reference to this Mentionable, which shouldn't be included in its #references.
def local_reference
self
end
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def has_mentioned? target
Note.cross_reference_exists?(target, local_reference)
end
def mentioned_users def mentioned_users
users = [] users = []
return users if mentionable_text.blank? return users if mentionable_text.blank?
...@@ -15,7 +50,7 @@ module Mentionable ...@@ -15,7 +50,7 @@ module Mentionable
matches.each do |match| matches.each do |match|
identifier = match.delete "@" identifier = match.delete "@"
if has_project if has_project
id = project.users_projects.joins(:user).where(users: { username: identifier }).pluck(:user_id).first id = project.team.members.find { |u| u.username == identifier }.try(:id)
else else
id = User.where(username: identifier).pluck(:id).first id = User.where(username: identifier).pluck(:id).first
end end
...@@ -24,14 +59,39 @@ module Mentionable ...@@ -24,14 +59,39 @@ module Mentionable
users.uniq users.uniq
end end
def mentionable_text # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
if self.class == Issue def references p = project, text = mentionable_text
description return [] if text.blank?
elsif self.class == Note ext = Gitlab::ReferenceExtractor.new
note ext.analyze(text)
else (ext.issues_for(p) + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference]
nil end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references! p = project, a = author, without = []
refs = references(p) - without
refs.each do |ref|
Note.create_cross_reference_note(ref, local_reference, a, p)
end end
end end
# If the mentionable_text field is about to change, locate any *added* references and create cross references for
# them. Invoke from an observer's #before_save implementation.
def notice_added_references p = project, a = author
ch = changed_attributes
original, mentionable_changed = "", false
self.class.mentionable_attrs.each do |attr|
if ch[attr]
original << ch[attr]
mentionable_changed = true
end
end
# Only proceed if the saved changes actually include a chance to an attr_mentionable field.
return unless mentionable_changed
preexisting = references(p, original)
create_cross_references!(p, a, preexisting)
end
end end
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# user_id :integer # user_id :integer
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# key :text # key :text
# title :string(255) # title :string(255)
# identifier :string(255)
# type :string(255) # type :string(255)
# fingerprint :string(255)
# #
class DeployKey < Key class DeployKey < Key
......
# Will be removed in 6.1 with tables
#
# == Schema Information
#
# Table name: user_teams
#
# id :integer not null, primary key
# name :string(255)
# path :string(255)
# owner_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# description :string(255) default(""), not null
#
class UserTeam < ActiveRecord::Base
attr_accessible :name, :description, :owner_id, :path
belongs_to :owner, class_name: User
has_many :user_team_project_relationships, dependent: :destroy
has_many :user_team_user_relationships, dependent: :destroy
has_many :projects, through: :user_team_project_relationships
has_many :members, through: :user_team_user_relationships, source: :user
validates :owner, presence: true
validates :name, presence: true, uniqueness: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed." }
validates :description, length: { within: 0..255 }
validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) }
scope :with_project, ->(project){ joins(:user_team_project_relationships).where(user_team_project_relationships: {project_id: project})}
scope :without_project, ->(project){ where("user_teams.id NOT IN (:ids)", ids: (a = with_project(project); a.blank? ? 0 : a))}
scope :created_by, ->(user){ where(owner_id: user) }
class << self
def search query
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
def global_id
'GLN'
end
def access_roles
UsersProject.access_roles
end
end
def to_param
path
end
def assign_to_projects(projects, access)
projects.each do |project|
assign_to_project(project, access)
end
end
def assign_to_project(project, access)
Gitlab::UserTeamManager.assign(self, project, access)
end
def resign_from_project(project)
Gitlab::UserTeamManager.resign(self, project)
end
def add_members(users, access, group_admin)
# reject existing users
users.reject! { |id| member_ids.include?(id.to_i) }
users.each do |user|
add_member(user, access, group_admin)
end
end
def add_member(user, access, group_admin)
Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin)
end
def remove_member(user)
Gitlab::UserTeamManager.remove_member_from_team(self, user)
end
def update_membership(user, options)
Gitlab::UserTeamManager.update_team_user_membership(self, user, options)
end
def update_project_access(project, permission)
Gitlab::UserTeamManager.update_project_greates_access(self, project, permission)
end
def max_project_access(project)
user_team_project_relationships.find_by_project_id(project).greatest_access
end
def human_max_project_access(project)
self.class.access_roles.invert[max_project_access(project)]
end
def default_projects_access(member)
user_team_user_relationships.find_by_user_id(member).permission
end
def human_default_projects_access(member)
self.class.access_roles.invert[default_projects_access(member)]
end
def admin?(member)
user_team_user_relationships.with_user(member).first.try(:group_admin?)
end
end
# Will be removed in 6.1 with tables
#
# == Schema Information
#
# Table name: user_team_project_relationships
#
# id :integer not null, primary key
# project_id :integer
# user_team_id :integer
# greatest_access :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class UserTeamProjectRelationship < ActiveRecord::Base
attr_accessible :greatest_access, :project_id, :user_team_id
belongs_to :user_team
belongs_to :project
validates :project, presence: true
validates :user_team, presence: true
validate :check_greatest_access
scope :with_project, ->(project){ where(project_id: project.id) }
def team_name
user_team.name
end
def human_max_access
UserTeam.access_roles.key(greatest_access)
end
private
def check_greatest_access
errors.add(:base, :incorrect_access_code) unless correct_access?
end
def correct_access?
return false if greatest_access.blank?
return true if UsersProject.access_roles.has_value?(greatest_access)
false
end
end
# Will be removed in 6.1 with tables
#
# == Schema Information
#
# Table name: user_team_user_relationships
#
# id :integer not null, primary key
# user_id :integer
# user_team_id :integer
# group_admin :boolean
# permission :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class UserTeamUserRelationship < ActiveRecord::Base
attr_accessible :group_admin, :permission, :user_id, :user_team_id
belongs_to :user_team
belongs_to :user
validates :user_team, presence: true
validates :user, presence: true
scope :with_user, ->(user) { where(user_id: user.id) }
def user_name
user.name
end
def access_human
UsersProject.access_roles.invert[permission]
end
end
...@@ -256,6 +256,10 @@ class Event < ActiveRecord::Base ...@@ -256,6 +256,10 @@ class Event < ActiveRecord::Base
target.commit_id target.commit_id
end end
def target_iid
target.respond_to?(:iid) ? target.iid : target_id
end
def note_short_commit_id def note_short_commit_id
note_commit_id[0..8] note_commit_id[0..8]
end end
...@@ -280,6 +284,14 @@ class Event < ActiveRecord::Base ...@@ -280,6 +284,14 @@ class Event < ActiveRecord::Base
end end
end end
def note_target_iid
if note_target.respond_to?(:iid)
note_target.iid
else
note_target_id
end.to_s
end
def wall_note? def wall_note?
target.noteable_type.blank? target.noteable_type.blank?
end end
......
...@@ -33,7 +33,7 @@ class Group < Namespace ...@@ -33,7 +33,7 @@ class Group < Namespace
end end
def owners def owners
@owners ||= (users_groups.owners.map(&:user) << owner) @owners ||= (users_groups.owners.map(&:user) << owner).uniq
end end
def add_users(user_ids, group_access) def add_users(user_ids, group_access)
......
...@@ -7,18 +7,19 @@ ...@@ -7,18 +7,19 @@
# assignee_id :integer # assignee_id :integer
# author_id :integer # author_id :integer
# project_id :integer # project_id :integer
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# position :integer default(0) # position :integer default(0)
# branch_name :string(255) # branch_name :string(255)
# description :text # description :text
# milestone_id :integer # milestone_id :integer
# state :string(255) # state :string(255)
# iid :integer
# #
class Issue < ActiveRecord::Base class Issue < ActiveRecord::Base
include Issuable include Issuable
include InternalId
belongs_to :project belongs_to :project
validates :project, presence: true validates :project, presence: true
...@@ -55,4 +56,10 @@ class Issue < ActiveRecord::Base ...@@ -55,4 +56,10 @@ class Issue < ActiveRecord::Base
# Both open and reopened issues should be listed as opened # Both open and reopened issues should be listed as opened
scope :opened, -> { with_state(:opened, :reopened) } scope :opened, -> { with_state(:opened, :reopened) }
# Mentionable overrides.
def gfm_reference
"issue ##{iid}"
end
end end
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# user_id :integer # user_id :integer
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# key :text # key :text
# title :string(255) # title :string(255)
# identifier :string(255)
# type :string(255) # type :string(255)
# fingerprint :string(255)
# #
require 'digest/md5' require 'digest/md5'
......
...@@ -3,34 +3,34 @@ ...@@ -3,34 +3,34 @@
# Table name: merge_requests # Table name: merge_requests
# #
# id :integer not null, primary key # id :integer not null, primary key
# target_project_id :integer not null
# target_branch :string(255) not null # target_branch :string(255) not null
# source_project_id :integer not null
# source_branch :string(255) not null # source_branch :string(255) not null
# source_project_id :integer not null
# author_id :integer # author_id :integer
# assignee_id :integer # assignee_id :integer
# title :string(255) # title :string(255)
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# st_commits :text(2147483647) # st_commits :text(2147483647)
# st_diffs :text(2147483647) # st_diffs :text(2147483647)
# milestone_id :integer # milestone_id :integer
# state :string(255) # state :string(255)
# merge_status :string(255) # merge_status :string(255)
# target_project_id :integer not null
# iid :integer
# #
require Rails.root.join("app/models/commit") require Rails.root.join("app/models/commit")
require Rails.root.join("lib/static_model") require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base class MergeRequest < ActiveRecord::Base
include Issuable include Issuable
include InternalId
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event, :description
attr_accessor :should_remove_source_branch attr_accessor :should_remove_source_branch
...@@ -254,6 +254,20 @@ class MergeRequest < ActiveRecord::Base ...@@ -254,6 +254,20 @@ class MergeRequest < ActiveRecord::Base
target_project target_project
end end
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues
if target_branch == project.default_branch
unmerged_commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id)
else
[]
end
end
# Mentionable override.
def gfm_reference
"merge request !#{iid}"
end
private private
def dump_commits(commits) def dump_commits(commits)
......
...@@ -10,9 +10,12 @@ ...@@ -10,9 +10,12 @@
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# state :string(255) # state :string(255)
# iid :integer
# #
class Milestone < ActiveRecord::Base class Milestone < ActiveRecord::Base
include InternalId
attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
attr_accessor :author_id_of_changes attr_accessor :author_id_of_changes
......
...@@ -4,15 +4,13 @@ module Network ...@@ -4,15 +4,13 @@ module Network
class Commit class Commit
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
attr_reader :refs
attr_accessor :time, :spaces, :parent_spaces attr_accessor :time, :spaces, :parent_spaces
def initialize(raw_commit, refs) def initialize(raw_commit)
@commit = Gitlab::Git::Commit.new(raw_commit) @commit = raw_commit
@time = -1 @time = -1
@spaces = [] @spaces = []
@parent_spaces = [] @parent_spaces = []
@refs = refs || []
end end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
......
require "grit"
module Network module Network
class Graph class Graph
attr_reader :days, :commits, :map, :notes attr_reader :days, :commits, :map, :notes, :repo
def self.max_count def self.max_count
@max_count ||= 650 @max_count ||= 650
...@@ -13,7 +11,7 @@ module Network ...@@ -13,7 +11,7 @@ module Network
@ref = ref @ref = ref
@commit = commit @commit = commit
@filter_ref = filter_ref @filter_ref = filter_ref
@repo = project.repo @repo = project.repository
@commits = collect_commits @commits = collect_commits
@days = index_commits @days = index_commits
...@@ -33,11 +31,9 @@ module Network ...@@ -33,11 +31,9 @@ module Network
# Get commits from repository # Get commits from repository
# #
def collect_commits def collect_commits
refs_cache = build_refs_cache
find_commits(count_to_display_commit_in_center).map do |commit| find_commits(count_to_display_commit_in_center).map do |commit|
# Decorate with app/model/network/commit.rb # Decorate with app/model/network/commit.rb
Network::Commit.new(commit, refs_cache[commit.id]) Network::Commit.new(commit)
end end
end end
...@@ -103,14 +99,13 @@ module Network ...@@ -103,14 +99,13 @@ module Network
def find_commits(skip = 0) def find_commits(skip = 0)
opts = { opts = {
date_order: true,
max_count: self.class.max_count, max_count: self.class.max_count,
skip: skip skip: skip
} }
ref = @ref if @filter_ref opts[:ref] = @commit.id if @filter_ref
Grit::Commit.find_all(@repo, ref, opts) @repo.find_commits(opts)
end end
def commits_sort_by_ref def commits_sort_by_ref
...@@ -126,15 +121,7 @@ module Network ...@@ -126,15 +121,7 @@ module Network
end end
def include_ref?(commit) def include_ref?(commit)
heads = commit.refs.select do |ref| commit.ref_names(@repo).include?(@ref)
ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
end
heads.map! do |head|
head.name
end
heads.include?(@ref)
end end
def find_free_parent_spaces(commit) def find_free_parent_spaces(commit)
...@@ -282,14 +269,5 @@ module Network ...@@ -282,14 +269,5 @@ module Network
leaves.push(commit) leaves.push(commit)
end end
end end
def build_refs_cache
refs_cache = {}
@repo.refs.each do |ref|
refs_cache[ref.commit.id] = [] unless refs_cache.include?(ref.commit.id)
refs_cache[ref.commit.id] << ref
end
refs_cache
end
end end
end end
...@@ -6,13 +6,14 @@ ...@@ -6,13 +6,14 @@
# note :text # note :text
# noteable_type :string(255) # noteable_type :string(255)
# author_id :integer # author_id :integer
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# project_id :integer # project_id :integer
# attachment :string(255) # attachment :string(255)
# line_code :string(255) # line_code :string(255)
# commit_id :string(255) # commit_id :string(255)
# noteable_id :integer # noteable_id :integer
# st_diff :text
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -23,6 +24,7 @@ class Note < ActiveRecord::Base ...@@ -23,6 +24,7 @@ class Note < ActiveRecord::Base
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id, attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code, :commit_id :attachment, :line_code, :commit_id
attr_mentionable :note
belongs_to :project belongs_to :project
belongs_to :noteable, polymorphic: true belongs_to :noteable, polymorphic: true
...@@ -53,15 +55,36 @@ class Note < ActiveRecord::Base ...@@ -53,15 +55,36 @@ class Note < ActiveRecord::Base
serialize :st_diff serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? } before_create :set_diff, if: ->(n) { n.line_code.present? }
def self.create_status_change_note(noteable, project, author, status) def self.create_status_change_note(noteable, project, author, status, source)
body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_"
create({
noteable: noteable,
project: project,
author: author,
note: body,
system: true
}, without_protection: true)
end
# +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
# Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
def self.create_cross_reference_note(noteable, mentioner, author, project)
create({ create({
noteable: noteable, noteable: noteable,
commit_id: (noteable.sha if noteable.respond_to? :sha),
project: project, project: project,
author: author, author: author,
note: "_Status changed to #{status}_" note: "_mentioned in #{mentioner.gfm_reference}_",
system: true
}, without_protection: true) }, without_protection: true)
end end
# Determine whether or not a cross-reference note already exists.
def self.cross_reference_exists?(noteable, mentioner)
where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any?
end
def commit_author def commit_author
@commit_author ||= @commit_author ||=
project.users.find_by_email(noteable.author_email) || project.users.find_by_email(noteable.author_email) ||
...@@ -190,6 +213,16 @@ class Note < ActiveRecord::Base ...@@ -190,6 +213,16 @@ class Note < ActiveRecord::Base
for_issue? || (for_merge_request? && !for_diff_line?) for_issue? || (for_merge_request? && !for_diff_line?)
end end
# Mentionable override.
def gfm_reference
noteable.gfm_reference
end
# Mentionable override.
def local_reference
noteable
end
def noteable_type_name def noteable_type_name
if noteable_type.present? if noteable_type.present?
noteable_type.downcase noteable_type.downcase
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# title :string(255) # title :string(255)
# content :text # content :text(2147483647)
# author_id :integer not null # author_id :integer not null
# project_id :integer # project_id :integer
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null # private :boolean default(TRUE), not null
......
# == 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
#
class PivotaltrackerService < Service
include HTTParty
validates :token, presence: true, if: :activated?
def title
'PivotalTracker'
end
def description
'Project Management Software (Source Commits Endpoint)'
end
def to_param
'pivotaltracker'
end
def fields
[
{ type: 'text', name: 'token', placeholder: '' }
]
end
def execute(push)
url = 'https://www.pivotaltracker.com/services/v5/source_commits'
push[:commits].each do |commit|
message = {
'source_commit' => {
'commit_id' => commit[:id],
'author' => commit[:author][:name],
'url' => commit[:url],
'message' => commit[:message]
}
}
PivotaltrackerService.post(
url,
body: message.to_json,
headers: {
'Content-Type' => 'application/json',
'X-TrackerToken' => token
}
)
end
end
end
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
# name :string(255) # name :string(255)
# path :string(255) # path :string(255)
# description :text # description :text
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# creator_id :integer # creator_id :integer
# default_branch :string(255) # default_branch :string(255)
# issues_enabled :boolean default(TRUE), not null # issues_enabled :boolean default(TRUE), not null
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
# 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)
# #
require "grit" require "grit"
...@@ -45,6 +46,7 @@ class Project < ActiveRecord::Base ...@@ -45,6 +46,7 @@ class Project < ActiveRecord::Base
has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', 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 :pivotaltracker_service, dependent: :destroy
has_one :hipchat_service, dependent: :destroy has_one :hipchat_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
...@@ -85,6 +87,7 @@ class Project < ActiveRecord::Base ...@@ -85,6 +87,7 @@ class Project < ActiveRecord::Base
: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: { within: 0..255 }
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
...@@ -136,10 +139,6 @@ class Project < ActiveRecord::Base ...@@ -136,10 +139,6 @@ class Project < ActiveRecord::Base
where(path: id, namespace_id: nil).last where(path: id, namespace_id: nil).last
end end
end end
def access_options
UsersProject.access_roles
end
end end
def team def team
...@@ -171,11 +170,7 @@ class Project < ActiveRecord::Base ...@@ -171,11 +170,7 @@ class Project < ActiveRecord::Base
end end
def to_param def to_param
if namespace
namespace.path + "/" + path namespace.path + "/" + path
else
path
end
end end
def web_url def web_url
...@@ -204,7 +199,7 @@ class Project < ActiveRecord::Base ...@@ -204,7 +199,7 @@ class Project < ActiveRecord::Base
def issue_exists?(issue_id) def issue_exists?(issue_id)
if used_default_issues_tracker? if used_default_issues_tracker?
self.issues.where(id: issue_id).first.present? self.issues.where(iid: issue_id).first.present?
else else
true true
end end
...@@ -229,7 +224,7 @@ class Project < ActiveRecord::Base ...@@ -229,7 +224,7 @@ class Project < ActiveRecord::Base
end end
def available_services_names def available_services_names
%w(gitlab_ci campfire hipchat) %w(gitlab_ci campfire hipchat pivotaltracker)
end end
def gitlab_ci? def gitlab_ci?
...@@ -402,11 +397,6 @@ class Project < ActiveRecord::Base ...@@ -402,11 +397,6 @@ class Project < ActiveRecord::Base
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end end
def project_access_human(member)
project_user_relation = self.users_projects.find_by_user_id(member.id)
self.class.access_options.key(project_user_relation.project_access)
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
def protected_branch? branch_name def protected_branch? branch_name
protected_branches_names.include?(branch_name) protected_branches_names.include?(branch_name)
...@@ -432,7 +422,9 @@ class Project < ActiveRecord::Base ...@@ -432,7 +422,9 @@ class Project < ActiveRecord::Base
begin begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
gitlab_shell.rm_satellites(old_path_with_namespace) gitlab_shell.rm_satellites(old_path_with_namespace)
ensure_satellite_exists
send_move_instructions send_move_instructions
reset_events_cache
rescue rescue
# Returning false does not rollback after_* transaction but gives # Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks # us information about failing some of tasks
...@@ -444,4 +436,19 @@ class Project < ActiveRecord::Base ...@@ -444,4 +436,19 @@ class Project < ActiveRecord::Base
raise Exception.new('repository cannot be renamed') raise Exception.new('repository cannot be renamed')
end end
end end
# Reset events cache related to this project
#
# Since we do cache @event we need to reset cache in special cases:
# * when project was moved
# * when project was renamed
# 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(project_id: self.id).
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
end end
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
# 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 # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# type :string(255) default("ProjectHook") # type :string(255) default("ProjectHook")
# service_id :integer # service_id :integer
# #
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# title :string(255) # title :string(255)
# content :text # content :text(2147483647)
# author_id :integer not null # author_id :integer not null
# project_id :integer # project_id :integer
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null # private :boolean default(TRUE), not null
......
...@@ -32,7 +32,15 @@ class ProjectTeam ...@@ -32,7 +32,15 @@ class ProjectTeam
end end
def find_tm(user_id) def find_tm(user_id)
project.users_projects.find_by_user_id(user_id) tm = project.users_projects.find_by_user_id(user_id)
# If user is not in project members
# we should check for group membership
if group && !tm
tm = group.users_groups.find_by_user_id(user_id)
end
tm
end end
def add_user(user, access) def add_user(user, access)
......
...@@ -18,6 +18,7 @@ class Repository ...@@ -18,6 +18,7 @@ class Repository
end end
def commit(id = nil) def commit(id = nil)
return nil unless raw_repository
commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit commit = Commit.new(commit) if commit
commit commit
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
# 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 # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# type :string(255) default("ProjectHook") # type :string(255) default("ProjectHook")
# service_id :integer # service_id :integer
# #
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# title :string(255) # title :string(255)
# content :text # content :text(2147483647)
# author_id :integer not null # author_id :integer not null
# project_id :integer # project_id :integer
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null # private :boolean default(TRUE), not null
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
# 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 # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# type :string(255) default("ProjectHook") # type :string(255) default("ProjectHook")
# service_id :integer # service_id :integer
# #
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# email :string(255) default(""), not null # email :string(255) default(""), not null
# encrypted_password :string(128) default(""), not null # encrypted_password :string(255) default(""), not null
# reset_password_token :string(255) # reset_password_token :string(255)
# reset_password_sent_at :datetime # reset_password_sent_at :datetime
# remember_created_at :datetime # remember_created_at :datetime
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
# last_sign_in_at :datetime # last_sign_in_at :datetime
# current_sign_in_ip :string(255) # current_sign_in_ip :string(255)
# last_sign_in_ip :string(255) # last_sign_in_ip :string(255)
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# name :string(255) # name :string(255)
# admin :boolean default(FALSE), not null # admin :boolean default(FALSE), not null
# projects_limit :integer default(10) # projects_limit :integer default(10)
...@@ -47,7 +47,7 @@ class User < ActiveRecord::Base ...@@ -47,7 +47,7 @@ class User < ActiveRecord::Base
:extern_uid, :provider, :password_expires_at, :extern_uid, :provider, :password_expires_at,
as: [:default, :admin] as: [:default, :admin]
attr_accessible :projects_limit, :can_create_team, :can_create_group, attr_accessible :projects_limit, :can_create_group,
as: :admin as: :admin
attr_accessor :force_random_password attr_accessor :force_random_password
...@@ -126,6 +126,17 @@ class User < ActiveRecord::Base ...@@ -126,6 +126,17 @@ class User < ActiveRecord::Base
after_transition any => :blocked do |user, transition| after_transition any => :blocked do |user, transition|
# Remove user from all projects and # Remove user from all projects and
user.users_projects.find_each do |membership| user.users_projects.find_each do |membership|
# skip owned resources
next if membership.project.owner == user
return false unless membership.destroy
end
# Remove user from all groups
user.users_groups.find_each do |membership|
# skip owned resources
next if membership.group.owners.include?(user)
return false unless membership.destroy return false unless membership.destroy
end end
end end
...@@ -148,6 +159,7 @@ class User < ActiveRecord::Base ...@@ -148,6 +159,7 @@ class User < ActiveRecord::Base
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) ) : scoped }
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 :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
...@@ -175,24 +187,32 @@ class User < ActiveRecord::Base ...@@ -175,24 +187,32 @@ class User < ActiveRecord::Base
end end
end end
def create_from_omniauth(auth, ldap = false) def search query
gitlab_auth.create_from_omniauth(auth, ldap) where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
end end
def find_or_new_for_omniauth(auth) def by_username_or_id(name_or_id)
gitlab_auth.find_or_new_for_omniauth(auth) if (name_or_id.is_a?(Integer))
User.find_by_id(name_or_id)
else
User.find_by_username(name_or_id)
end end
def find_for_ldap_auth(auth, signed_in_resource = nil)
gitlab_auth.find_for_ldap_auth(auth, signed_in_resource)
end end
def gitlab_auth def build_user(attrs = {}, options= {})
Gitlab::Auth.new if options[:as] == :admin
User.new(defaults.merge(attrs.symbolize_keys), options)
else
User.new(attrs, options).with_defaults
end
end end
def search query def defaults
where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%") {
projects_limit: Gitlab.config.gitlab.default_projects_limit,
can_create_group: Gitlab.config.gitlab.default_can_create_group,
theme_id: Gitlab::Theme::MARS
}
end end
end end
...@@ -204,15 +224,6 @@ class User < ActiveRecord::Base ...@@ -204,15 +224,6 @@ class User < ActiveRecord::Base
username username
end end
def with_defaults
tap do |u|
u.projects_limit = Gitlab.config.gitlab.default_projects_limit
u.can_create_group = Gitlab.config.gitlab.default_can_create_group
u.can_create_team = Gitlab.config.gitlab.default_can_create_team
u.theme_id = Gitlab::Theme::MARS
end
end
def notification def notification
@notification ||= Notification.new(self) @notification ||= Notification.new(self)
end end
...@@ -323,7 +334,7 @@ class User < ActiveRecord::Base ...@@ -323,7 +334,7 @@ class User < ActiveRecord::Base
end end
def several_namespaces? def several_namespaces?
namespaces.many? namespaces.many? || owned_groups.any?
end end
def namespace_id def namespace_id
...@@ -374,4 +385,18 @@ class User < ActiveRecord::Base ...@@ -374,4 +385,18 @@ class User < ActiveRecord::Base
def requires_ldap_check? def requires_ldap_check?
!last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
end end
def solo_owned_groups
@solo_owned_groups ||= owned_groups.select do |group|
group.owners == [self]
end
end
def with_defaults
User.defaults.each do |k, v|
self.send("#{k}=", v)
end
self
end
end end
...@@ -8,25 +8,15 @@ ...@@ -8,25 +8,15 @@
# user_id :integer not null # user_id :integer not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# notification_level :integer default(3), not null
# #
class UsersGroup < ActiveRecord::Base class UsersGroup < ActiveRecord::Base
include Notifiable include Notifiable
include Gitlab::Access
GUEST = 10
REPORTER = 20
DEVELOPER = 30
MASTER = 40
OWNER = 50
def self.group_access_roles def self.group_access_roles
{ Gitlab::Access.options_with_owner
"Guest" => GUEST,
"Reporter" => REPORTER,
"Developer" => DEVELOPER,
"Master" => MASTER,
"Owner" => OWNER
}
end end
attr_accessible :group_access, :user_id attr_accessible :group_access, :user_id
...@@ -50,7 +40,7 @@ class UsersGroup < ActiveRecord::Base ...@@ -50,7 +40,7 @@ class UsersGroup < ActiveRecord::Base
delegate :name, :username, :email, to: :user, prefix: true delegate :name, :username, :email, to: :user, prefix: true
def human_access def access_field
UsersGroup.group_access_roles.key(self.group_access) group_access
end end
end end
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
# id :integer not null, primary key # id :integer not null, primary key
# user_id :integer not null # user_id :integer not null
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# project_access :integer default(0), not null # project_access :integer default(0), not null
# notification_level :integer default(3), not null # notification_level :integer default(3), not null
# #
...@@ -14,11 +14,7 @@ ...@@ -14,11 +14,7 @@
class UsersProject < ActiveRecord::Base class UsersProject < ActiveRecord::Base
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Notifiable include Notifiable
include Gitlab::Access
GUEST = 10
REPORTER = 20
DEVELOPER = 30
MASTER = 40
attr_accessible :user, :user_id, :project_access attr_accessible :user, :user_id, :project_access
...@@ -27,7 +23,7 @@ class UsersProject < ActiveRecord::Base ...@@ -27,7 +23,7 @@ class UsersProject < ActiveRecord::Base
validates :user, presence: true validates :user, presence: true
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" } validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true validates :project_access, inclusion: { in: Gitlab::Access.values }, presence: true
validates :project, presence: true validates :project, presence: true
delegate :name, :username, :email, to: :user, prefix: true delegate :name, :username, :email, to: :user, prefix: true
...@@ -103,27 +99,19 @@ class UsersProject < ActiveRecord::Base ...@@ -103,27 +99,19 @@ class UsersProject < ActiveRecord::Base
end end
def roles_hash def roles_hash
{ Gitlab::Access.sym_options
guest: GUEST,
reporter: REPORTER,
developer: DEVELOPER,
master: MASTER
}
end end
def access_roles def access_roles
{ Gitlab::Access.options
"Guest" => GUEST,
"Reporter" => REPORTER,
"Developer" => DEVELOPER,
"Master" => MASTER
}
end end
end end
def project_access_human def access_field
Project.access_options.key(self.project_access) project_access
end end
alias_method :human_access, :project_access_human def owner?
project.owner == user
end
end end
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
# 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 # created_at :datetime not null
# updated_at :datetime # updated_at :datetime not null
# type :string(255) default("ProjectHook") # type :string(255) default("ProjectHook")
# service_id :integer # service_id :integer
# #
......
...@@ -5,8 +5,8 @@ class ActivityObserver < BaseObserver ...@@ -5,8 +5,8 @@ class ActivityObserver < BaseObserver
event_author_id = record.author_id event_author_id = record.author_id
if record.kind_of?(Note) if record.kind_of?(Note)
# Skip system status notes like 'status changed to close' # Skip system notes, like status changes and cross-references.
return true if record.note.include?("_Status changed to ") return true if record.system?
# Skip wall notes to prevent spamming of dashboard # Skip wall notes to prevent spamming of dashboard
return true if record.noteable_type.blank? return true if record.noteable_type.blank?
......
...@@ -10,4 +10,8 @@ class BaseObserver < ActiveRecord::Observer ...@@ -10,4 +10,8 @@ class BaseObserver < ActiveRecord::Observer
def current_user def current_user
Thread.current[:current_user] Thread.current[:current_user]
end end
def current_commit
Thread.current[:current_commit]
end
end end
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)
end end
def after_close(issue, transition) def after_close(issue, transition)
...@@ -17,12 +19,14 @@ class IssueObserver < BaseObserver ...@@ -17,12 +19,14 @@ class IssueObserver < BaseObserver
if issue.is_being_reassigned? if issue.is_being_reassigned?
notification.reassigned_issue(issue, current_user) notification.reassigned_issue(issue, current_user)
end end
issue.notice_added_references(issue.project, current_user)
end end
protected protected
# Create issue note with service comment like 'Status changed to closed' # Create issue note with service comment like 'Status changed to closed'
def create_note(issue) def create_note(issue)
Note.create_status_change_note(issue, issue.project, current_user, issue.state) Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end end
end end
...@@ -7,11 +7,13 @@ class MergeRequestObserver < ActivityObserver ...@@ -7,11 +7,13 @@ 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)
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) 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)
end end
...@@ -33,11 +35,13 @@ class MergeRequestObserver < ActivityObserver ...@@ -33,11 +35,13 @@ class MergeRequestObserver < ActivityObserver
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) Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
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)
end end
def create_event(record, status) def create_event(record, status)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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