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/*
backups/*
*.swp
public/uploads/
.ruby-version
.ruby-gemset
.rvmrc
.rbenv-version
.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
- Feature: Replace teams with group membership
We introduce group membership in 6.0 as a replacement for teams.
......@@ -54,7 +76,7 @@ v 5.4.0
- Notify mentioned users with email
v 5.3.0
- Refactored services
- Refactored services
- Campfire service added
- HipChat service added
- Fixed bug with LDAP + git over http
......
......@@ -23,7 +23,7 @@ gem 'omniauth-github'
# Extracting information from a git repository
# 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
gem 'gitlab-grack', '~> 1.0.1', require: 'grack'
......@@ -135,7 +135,7 @@ group :assets do
gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.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 "font-awesome-rails"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
......@@ -188,7 +188,7 @@ group :development, :test do
gem 'rb-inotify', require: linux_only('rb-inotify')
# PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.3.0'
gem 'poltergeist', '~> 1.4.1'
gem 'spork', '~> 1.0rc'
gem 'jasmine'
......
......@@ -6,12 +6,6 @@ GIT
activerecord (>= 2.3.0)
rake (>= 0.8.7)
GIT
remote: https://github.com/gitlabhq/raphael-rails.git
revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
specs:
raphael-rails (2.1.0)
GEM
remote: https://rubygems.org/
specs:
......@@ -78,6 +72,7 @@ GEM
railties (>= 3.0)
sass-rails (>= 3.2)
chunky_png (1.2.8)
cliver (0.2.1)
code_analyzer (0.3.2)
sexp_processor
coderay (1.0.9)
......@@ -108,7 +103,7 @@ GEM
d3_rails (3.1.10)
railties (>= 3.1.0)
daemons (1.1.9)
database_cleaner (1.0.1)
database_cleaner (1.1.1)
debug_inspector (0.0.2)
descendants_tracker (0.0.1)
devise (2.2.5)
......@@ -138,8 +133,6 @@ GEM
multipart-post (~> 1.1)
faraday_middleware (0.9.0)
faraday (>= 0.7.4, < 0.9)
faye-websocket (0.4.7)
eventmachine (>= 0.12.0)
ffaker (1.16.1)
ffi (1.9.0)
fog (1.3.1)
......@@ -186,7 +179,7 @@ GEM
gitlab-pygments.rb (0.3.2)
posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.1.0)
gitlab_git (2.1.0)
gitlab_git (2.3.1)
activesupport (~> 3.2.13)
github-linguist (~> 2.3.4)
gitlab-grit (~> 2.6.0)
......@@ -281,7 +274,7 @@ GEM
mime-types (~> 1.16)
treetop (~> 1.4.8)
method_source (0.8.1)
mime-types (1.23)
mime-types (1.25)
minitest (4.7.4)
modernizr (2.6.2)
sprockets (~> 2.0)
......@@ -321,10 +314,11 @@ GEM
omniauth-oauth (~> 1.0)
orm_adapter (0.4.0)
pg (0.15.1)
poltergeist (1.3.0)
poltergeist (1.4.1)
capybara (~> 2.1.0)
faye-websocket (>= 0.4.4, < 0.5.0)
http_parser.rb (~> 0.5.3)
cliver (~> 0.2.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
polyglot (0.3.3)
posix-spawn (0.3.6)
pry (0.9.12.2)
......@@ -380,6 +374,7 @@ GEM
thor (>= 0.14.6, < 2.0)
raindrops (0.11.0)
rake (10.1.0)
raphael-rails (2.1.2)
rb-fsevent (0.9.3)
rb-inotify (0.9.0)
ffi (>= 0.5.0)
......@@ -389,24 +384,24 @@ GEM
json (~> 1.4)
redcarpet (2.2.2)
redis (3.0.4)
redis-actionpack (3.2.3)
actionpack (~> 3.2.3)
redis-rack (~> 1.4.0)
redis-actionpack (3.2.4)
actionpack (~> 3.2.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-activesupport (3.2.3)
activesupport (~> 3.2.3)
redis-store (~> 1.1.0)
redis-namespace (1.3.0)
redis-namespace (1.3.1)
redis (~> 3.0.0)
redis-rack (1.4.2)
rack (~> 1.4.1)
redis-store (~> 1.1.0)
redis-rails (3.2.3)
redis-actionpack (~> 3.2.3)
redis-activesupport (~> 3.2.3)
redis-store (~> 1.1.0)
redis-store (1.1.3)
redis (>= 2.2.0)
redis-rack (1.4.4)
rack (~> 1.4.0)
redis-store (~> 1.1.4)
redis-rails (3.2.4)
redis-actionpack (~> 3.2.4)
redis-activesupport (~> 3.2.4)
redis-store (~> 1.1.4)
redis-store (1.1.4)
redis (>= 2.2)
ref (1.0.5)
rest-client (1.6.7)
mime-types (>= 1.16)
......@@ -426,7 +421,7 @@ GEM
rspec-expectations (~> 2.13.0)
rspec-mocks (~> 2.13.0)
ruby-hmac (0.4.0)
ruby-progressbar (1.1.1)
ruby-progressbar (1.2.0)
rubyntlm (0.1.1)
rubyzip (0.9.9)
safe_yaml (0.9.3)
......@@ -455,11 +450,11 @@ GEM
sexp_processor (4.2.1)
shoulda-matchers (2.1.0)
activesupport (>= 3.0.0)
sidekiq (2.12.4)
sidekiq (2.14.0)
celluloid (>= 0.14.1)
connection_pool (>= 1.0.0)
json
redis (>= 3.0)
redis (>= 3.0.4)
redis-namespace
simple_oauth (0.1.9)
simplecov (0.7.1)
......@@ -539,6 +534,7 @@ GEM
addressable (>= 2.2.7)
crack (>= 0.3.2)
websocket (1.0.7)
websocket-driver (0.3.0)
xpath (2.0.0)
nokogiri (~> 1.3)
yajl-ruby (1.1.0)
......@@ -576,7 +572,7 @@ DEPENDENCIES
gitlab-gollum-lib (~> 1.0.1)
gitlab-grack (~> 1.0.1)
gitlab-pygments.rb (~> 0.3.2)
gitlab_git (= 2.1.0)
gitlab_git (= 2.3.1)
gitlab_meta (= 6.0)
gitlab_omniauth-ldap (= 1.0.3)
gon
......@@ -605,14 +601,14 @@ DEPENDENCIES
omniauth-google-oauth2
omniauth-twitter
pg
poltergeist (~> 1.3.0)
poltergeist (~> 1.4.1)
pry
quiet_assets (~> 1.0.1)
rack-mini-profiler
rails (= 3.2.13)
rails-dev-tweaks
rails_best_practices
raphael-rails!
raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
redcarpet (~> 2.2.2)
......
......@@ -2,23 +2,47 @@
## 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
### 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
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.
### 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 (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).
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.
## Priorities of the issue team
Priority (from high to low):
1. Mentioning people (critical)
2. Workflow labels (normal)
3. Functional labels (minor)
4. Assigning issues (avoid if possible)
1. Mentioning people (very important)
2. Workflow labels
3. Functional labels (less important)
4. Assigning issues (optional)
## Mentioning people
The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](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.
## 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 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
......@@ -29,6 +53,10 @@ Priority (from high to low):
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
- 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)
......@@ -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))
- 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
### Improperly formatted issue
......
......@@ -14,14 +14,12 @@
* powered by Ruby on Rails
* 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
* [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
* [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch)
* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
* [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available), gems are updated in major releases of GitLab.
......@@ -39,11 +37,10 @@
### Requirements
* Ubuntu/Debian**
* ruby 1.9.3
* ruby 1.9.3+
* git 1.7.10+
* redis 2.0+
* MySQL or PostgreSQL
* git
* gitlab-shell
* redis
** 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
* [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
......
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
......@@ -23,10 +23,16 @@ class Admin
e.preventDefault()
$(this).hide()
modal.show()
$('.change-owner-cancel-link').bind "click", (e) ->
e.preventDefault()
modal.hide()
$('.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
class Dashboard
constructor: ->
Pager.init 20, true
@initSidebarTab()
$(".event_filter_link").bind "click", (event) =>
event.preventDefault()
@toggleFilter($(event.currentTarget))
@reloadActivities()
$(".dash-filter").keyup ->
terms = $(this).val()
uiBox = $(this).parents('.ui-box').first()
......@@ -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: ->
key = "dashboard_sidebar_filter"
......
......@@ -18,12 +18,15 @@ class Dispatcher
switch page
when 'projects:issues:index'
Issues.init()
when 'projects:issues:new', 'projects:merge_requests:new'
GitLab.GfmAutoComplete.setup()
when 'dashboard:show'
new Dashboard()
new Activities()
when 'projects:commit:show'
new Commit()
when 'groups:show', 'projects:show'
Pager.init(20, true)
new Activities()
when 'projects:new', 'projects:edit'
new Project()
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 =
tpl: @Issues.template
callbacks:
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", =>
$.getJSON(@dataSource).done (data) ->
......
......@@ -11,7 +11,7 @@ class MergeRequest
constructor: (@opts) ->
this.$el = $('.merge-request')
@diffs_loaded = false
@diffs_loaded = if @opts.action == 'diffs' then true else false
@commits_loaded = false
this.activateTab(@opts.action)
......
$ ->
$('.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
$('.edit_user').submit()
# Go up the hierarchy and show the corresponding submission feedback element
$(@).closest('fieldset').find('.update-feedback').show('highlight', {color: '#DFF0D8'}, 500)
new Flash("Appearance settings saved", "notice")
$('.update-username form').on 'ajax:before', ->
$('.loading-gif').show()
......@@ -17,6 +13,6 @@ $ ->
$('.update-username form').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton()
$(this).find('.loading-gif').hide()
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton()
......@@ -35,6 +35,7 @@ $ ->
$('a, button', scope).removeClass 'active'
$(@).addClass 'active'
$('#project_clone', scope).val $(@).data 'clone'
$(".clone").text("").append 'git remote add origin ' + $(@).data 'clone'
# Ref switcher
$('.project-refs-select').on 'change', ->
......
......@@ -55,6 +55,7 @@ table a code {
bottom: 0;
width: 100%;
opacity: 0.8;
z-index: 100;
.flash-notice {
background: #49C;
......@@ -376,3 +377,12 @@ table {
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 @@
.hint { font-style: italic; color: #999; }
.light { color: #888 }
.tiny { font-weight: normal }
.vtop { vertical-align: top; }
.vtop { vertical-align: top !important; }
/** ALERT MESSAGES **/
......@@ -56,23 +56,6 @@
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 {
overflow: visible;
......
......@@ -6,10 +6,9 @@ form {
}
}
input {
&.input-xpadding {
padding: 6px 10px;
}
input.input-xpadding,
.add-on.input-xpadding {
padding: 6px 10px;
}
.control-group {
......@@ -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 @@
h2 { margin-top: 25px;}
h3 { margin-top: 20px;}
h4 { margin-top: 15px;}
blockquote p {
color: #888;
font-size: 14px;
line-height: 1.5;
}
}
@mixin page-title {
......
......@@ -22,6 +22,7 @@
background: #F1F1F1;
color: $style_color;
font-weight: bold;
text-shadow: 0 1px 1px #fff;
}
&.nav-stacked-menu {
......
......@@ -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 {
color: $style_color;
}
......@@ -463,24 +432,6 @@
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 {
padding: 0;
}
......@@ -496,3 +447,61 @@ li.commit {
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 @@
}
.project-row, .group-row {
padding: 12px 15px !important;
padding: 10px 15px !important;
.namespace-name {
color: #666;
......@@ -77,7 +77,6 @@
font-size: 16px;
}
.arrow {
float: right;
padding: 10px 5px;
......
......@@ -56,10 +56,6 @@
margin-left: 35px;
margin-right: 100px;
.commit p {
color: #666;
padding-top: 5px;
}
.event-info {
color: #666;
}
......@@ -80,6 +76,10 @@
margin-left: 0px;
max-width: 200px;
}
p:last-child {
margin-bottom: 0;
}
}
.event-note-icon {
color: #777;
......@@ -103,13 +103,6 @@
}
}
ul {
.avatar {
width: 18px;
margin: 2px 4px;
}
}
&:last-child { border:none }
.event_commits {
......@@ -120,12 +113,14 @@
background: transparent;
padding: 3px;
border: none;
font-size: 12px;
color: #666;
.commit-row-title {
font-size: 12px;
}
}
&.commits-stat {
display: block;
padding: 3px;
margin-top: 3px;
&:hover {
background: none;
......
......@@ -64,13 +64,6 @@
margin: 0;
padding: 0;
padding: 5px 0;
.avatar { position:relative }
.commit-author-name,
.dash,
.committed_ago,
.browse_code_link_holder {
display: none;
}
list-style: none;
&:hover {
background: none;
......@@ -117,7 +110,7 @@
.merge-request-angle {
text-align: center;
margin-top: 45px;
margin: 0;
}
.merge-request-form-info {
......
.save-status-fixed {
position: fixed;
left: 20px;
bottom: 50px;
}
.update-notifications {
margin-bottom: 0;
label {
......
......@@ -36,6 +36,12 @@
}
}
.project-public-holder {
.help-inline {
padding-top: 7px;
}
}
.save-project-loader {
img {
margin-top: 50px;
......
.application-theme, .code-preview-theme {
.update-feedback {
color: #468847;
float: right;
}
}
.themes_opts {
padding-left: 20px;
......
......@@ -15,6 +15,12 @@
.tree-table {
@include border-radius(0);
.tree-item {
td {
padding: 8px 10px;
strong {
font-weight: normal;
}
}
&:hover {
td {
background: $hover;
......
......@@ -17,6 +17,7 @@
&.navbar-gitlab {
.navbar-inner {
background: #547;
border-bottom: 1px solid #435;
.app_logo {
&:hover {
background-color: #435;
......
......@@ -17,6 +17,7 @@
&.navbar-gitlab {
.navbar-inner {
background: #373737;
border-bottom: 1px solid #272727;
.app_logo {
&:hover {
background-color: #272727;
......
......@@ -17,6 +17,7 @@
&.navbar-gitlab {
.navbar-inner {
background: #345;
border-bottom: 1px solid #234;
.app_logo {
&:hover {
background-color: #234;
......
......@@ -20,7 +20,8 @@ class CommitLoadContext < BaseContext
result[:notes_count] = project.notes.for_commit_id(commit.id).count
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
result[:suppress_diff] = true
result[:status] = :huge_commit
......
......@@ -16,8 +16,9 @@ module Projects
wiki_enabled: default_features.wiki,
wall_enabled: default_features.wall,
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))
......
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
end
def new
@user = User.new.with_defaults
@user = User.build_user
end
def edit
......@@ -44,7 +44,7 @@ class Admin::UsersController < Admin::ApplicationController
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.created_by_id = current_user.id
......@@ -83,9 +83,10 @@ class Admin::UsersController < Admin::ApplicationController
end
def destroy
if user.personal_projects.count > 0
redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
end
# 1. Remove groups where user is the only owner
user.solo_owned_groups.map(&:destroy)
# 2. Remove user with all authored content including personal projects
user.destroy
respond_to do |format|
......
......@@ -96,10 +96,6 @@ class ApplicationController < ActionController::Base
return access_denied! unless can?(current_user, :push_code, project)
end
def authorize_create_team!
return access_denied! unless can?(current_user, :create_team, nil)
end
def access_denied!
render "errors/access_denied", layout: "errors", status: 404
end
......@@ -156,7 +152,7 @@ class ApplicationController < ActionController::Base
end
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
end
end
......@@ -169,4 +165,9 @@ class ApplicationController < ActionController::Base
current_user.save
end
end
def event_filter
filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters)
end
end
......@@ -33,9 +33,13 @@ class DashboardController < ApplicationController
current_user.owned_projects
else
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)
@groups = current_user.authorized_groups
@projects = @projects.tagged_with(params[:label]) if params[:label].present?
@projects = @projects.page(params[:page]).per(30)
......@@ -66,9 +70,4 @@ class DashboardController < ApplicationController
def load_projects
@projects = current_user.authorized_projects.sorted_by_activity
end
def event_filter
filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters)
end
end
......@@ -31,7 +31,9 @@ class GroupsController < ApplicationController
end
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
@shared_projects = @group.shared_projects
......@@ -110,7 +112,7 @@ class GroupsController < ApplicationController
# Dont allow unauthorized access to 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
end
end
......
......@@ -16,38 +16,42 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
def ldap
# We only find ourselves here if the authentication to LDAP was successful.
@user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user)
if @user.persisted?
@user.remember_me = true
end
# We only find ourselves here
# if the authentication to LDAP was successful.
@user = Gitlab::LDAP::User.find_or_create(oauth)
@user.remember_me = true if @user.persisted?
Gitlab::LDAP::Access.new.update_permissions(@user)
sign_in_and_redirect @user
sign_in_and_redirect(@user)
end
private
def handle_omniauth
oauth = request.env['omniauth.auth']
provider, uid = oauth['provider'], oauth['uid']
if current_user
# Change a logged-in user's authentication method:
current_user.extern_uid = uid
current_user.provider = provider
current_user.extern_uid = oauth['uid']
current_user.provider = oauth['provider']
current_user.save
redirect_to profile_path
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
sign_in_and_redirect @user
sign_in_and_redirect(@user)
else
flash[:notice] = "There's no such user!"
redirect_to new_user_session_path
end
end
end
def oauth
@oauth ||= request.env['omniauth.auth']
end
end
......@@ -18,6 +18,7 @@ class Projects::CommitController < Projects::ApplicationController
end
@suppress_diff = result[:suppress_diff]
@force_suppress_diff = result[:force_suppress_diff]
@note = result[:note]
@line_notes = result[:line_notes]
......
......@@ -8,13 +8,17 @@ class Projects::CompareController < Projects::ApplicationController
end
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
@commit = compare.commit
@diffs = compare.diffs
@refs_are_same = compare.same
@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
def create
......
......@@ -91,7 +91,11 @@ class Projects::IssuesController < Projects::ApplicationController
protected
def issue
@issue ||= @project.issues.find(params[:id])
@issue ||= begin
@project.issues.find_by_iid!(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_old
end
end
def authorize_modify_issue!
......@@ -109,4 +113,20 @@ class Projects::IssuesController < Projects::ApplicationController
def issues_filtered
@issues = Issues::ListContext.new(project, current_user, params).execute
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
......@@ -3,6 +3,7 @@ require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :module_enabled
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 :define_show_vars, only: [:show, :diffs]
......@@ -39,6 +40,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@comments_target = {noteable_type: 'MergeRequest',
noteable_id: @merge_request.id}
@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
def new
......@@ -132,7 +137,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
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
def authorize_modify_merge_request!
......
......@@ -81,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController
protected
def milestone
@milestone ||= @project.milestones.find(params[:id])
@milestone ||= @project.milestones.find_by_iid!(params[:id])
end
def authorize_admin_milestone!
......
......@@ -8,10 +8,6 @@ class Projects::NetworkController < Projects::ApplicationController
before_filter :require_non_empty_project
def show
if @options[:q]
@commit = @project.repository.commit(@options[:q]) || @commit
end
respond_to do |format|
format.html
......
......@@ -11,9 +11,17 @@ class Projects::RawController < Projects::ApplicationController
@blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path)
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(
@blob.data,
type: @blob.mime_type,
type: type,
disposition: 'inline',
filename: @blob.name
)
......
......@@ -55,7 +55,10 @@ class ProjectsController < Projects::ApplicationController
def show
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
# Normally it defined on push or during creation
......@@ -104,7 +107,7 @@ class ProjectsController < Projects::ApplicationController
def autocomplete_sources
@suggestions = {
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 } }
}
......
......@@ -2,9 +2,6 @@ class RegistrationsController < Devise::RegistrationsController
before_filter :signup_enabled?
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
respond_to do |format|
......
......@@ -235,4 +235,11 @@ module ApplicationHelper
"Search"
end
end
def first_line(str)
lines = str.split("\n")
line = lines.first
line += "..." if lines.size > 1
line
end
end
......@@ -56,8 +56,9 @@ module CommitsHelper
end
end
def commit_to_html commit, project
escape_javascript(render 'projects/commits/commit', commit: commit, project: project) unless commit.nil?
def commit_to_html(commit, project, inline = true)
template = inline ? "inline_commit" : "commit"
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
end
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
end
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]
end
end
......@@ -109,7 +109,7 @@ module EventsHelper
else
link_to event_note_target_path(event) 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
......@@ -123,6 +123,8 @@ module EventsHelper
end
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
module GraphHelper
def get_refs(commit)
def get_refs(repo, commit)
refs = ""
refs += commit.refs.collect{|r|r.name}.join(" ") if commit.refs
refs += commit.ref_names(repo).join(" ")
# append note count
refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
......
......@@ -37,23 +37,23 @@ module IssuesHelper
end
end
def url_for_issue(issue_id)
def url_for_issue(issue_iid)
return "" if @project.nil?
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
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(':issues_tracker_id', @project.issues_tracker_id.to_s)
end
end
def title_for_issue(issue_id)
def title_for_issue(issue_iid)
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
else
""
......
......@@ -4,4 +4,16 @@ module ProfileHelper
'active'
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
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
def new_issue_email(recipient_id, issue_id)
@issue = Issue.find(issue_id)
@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
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
@issue = Issue.find(issue_id)
@previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id
@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
def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
......@@ -18,7 +18,7 @@ module Emails
@project = @issue.project
@updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id),
subject: subject("Closed issue ##{@issue.id}", @issue.title))
subject: subject("Closed issue ##{@issue.iid}", @issue.title))
end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
......@@ -27,7 +27,7 @@ module Emails
@project = @issue.project
@updated_by = User.find updated_by_user_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
......@@ -2,24 +2,24 @@ module Emails
module MergeRequests
def new_merge_request_email(recipient_id, 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
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
@merge_request = MergeRequest.find(merge_request_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
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_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
def merged_merge_request_email(recipient_id, 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
......
......@@ -11,14 +11,14 @@ module Emails
@note = Note.find(note_id)
@issue = @note.noteable
@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
def note_merge_request_email(recipient_id, note_id)
@note = Note.find(note_id)
@merge_request = @note.noteable
@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
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
subject: subject("access to project was granted"))
end
def project_was_moved_email(project_id, user_id)
@user = User.find user_id
@project = Project.find project_id
......
......@@ -3,6 +3,8 @@ class Notify < ActionMailer::Base
include Emails::MergeRequests
include Emails::Notes
include Emails::Projects
include Emails::Profile
include Emails::Groups
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
......@@ -20,18 +22,6 @@ class Notify < ActionMailer::Base
delay_for(2.seconds)
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
# Look up a User by their ID and return their email address
......
......@@ -2,6 +2,7 @@ class Ability
class << self
def allowed(user, subject)
return [] unless user.kind_of?(User)
return [] if user.blocked?
case subject.class.name
when "Project" then project_abilities(user, subject)
......@@ -134,6 +135,10 @@ class Ability
def group_abilities user, group
rules = []
if group.users.include?(user)
rules << :read_group
end
# Only group owner and administrators can manage group
if group.owners.include?(user) || user.admin?
rules << [
......
......@@ -2,16 +2,45 @@ class Commit
include ActiveModel::Conversion
include StaticModel
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
#
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)
commits.map { |c| self.new(c) }
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
def initialize(raw_commit)
......@@ -24,6 +53,19 @@ class Commit
@raw.id
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
#
# Example
......@@ -65,6 +107,29 @@ class Commit
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)
@raw.send(m, *args, &block)
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
scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
delegate :name,
:email,
to: :author,
......@@ -36,6 +37,8 @@ module Issuable
prefix: true
attr_accessor :author_id_of_changes
attr_mentionable :title, :description
end
module ClassMethods
......
# == 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
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
users = []
return users if mentionable_text.blank?
......@@ -15,7 +50,7 @@ module Mentionable
matches.each do |match|
identifier = match.delete "@"
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
id = User.where(username: identifier).pluck(:id).first
end
......@@ -24,14 +59,39 @@ module Mentionable
users.uniq
end
def mentionable_text
if self.class == Issue
description
elsif self.class == Note
note
else
nil
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def references p = project, text = mentionable_text
return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new
ext.analyze(text)
(ext.issues_for(p) + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference]
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
# 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
......@@ -2,14 +2,14 @@
#
# Table name: keys
#
# id :integer not null, primary key
# user_id :integer
# created_at :datetime
# updated_at :datetime
# key :text
# title :string(255)
# identifier :string(255)
# type :string(255)
# id :integer not null, primary key
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# key :text
# title :string(255)
# type :string(255)
# fingerprint :string(255)
#
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
target.commit_id
end
def target_iid
target.respond_to?(:iid) ? target.iid : target_id
end
def note_short_commit_id
note_commit_id[0..8]
end
......@@ -280,6 +284,14 @@ class Event < ActiveRecord::Base
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?
target.noteable_type.blank?
end
......
......@@ -33,7 +33,7 @@ class Group < Namespace
end
def owners
@owners ||= (users_groups.owners.map(&:user) << owner)
@owners ||= (users_groups.owners.map(&:user) << owner).uniq
end
def add_users(user_ids, group_access)
......
......@@ -7,18 +7,19 @@
# assignee_id :integer
# author_id :integer
# project_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# position :integer default(0)
# branch_name :string(255)
# description :text
# milestone_id :integer
# state :string(255)
# iid :integer
#
class Issue < ActiveRecord::Base
include Issuable
include InternalId
belongs_to :project
validates :project, presence: true
......@@ -55,4 +56,10 @@ class Issue < ActiveRecord::Base
# Both open and reopened issues should be listed as opened
scope :opened, -> { with_state(:opened, :reopened) }
# Mentionable overrides.
def gfm_reference
"issue ##{iid}"
end
end
......@@ -2,14 +2,14 @@
#
# Table name: keys
#
# id :integer not null, primary key
# user_id :integer
# created_at :datetime
# updated_at :datetime
# key :text
# title :string(255)
# identifier :string(255)
# type :string(255)
# id :integer not null, primary key
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# key :text
# title :string(255)
# type :string(255)
# fingerprint :string(255)
#
require 'digest/md5'
......
......@@ -2,35 +2,35 @@
#
# Table name: merge_requests
#
# id :integer not null, primary key
# target_project_id :integer not null
# target_branch :string(255) not null
# source_project_id :integer not null
# source_branch :string(255) not null
# author_id :integer
# assignee_id :integer
# title :string(255)
# created_at :datetime
# updated_at :datetime
# st_commits :text(2147483647)
# st_diffs :text(2147483647)
# milestone_id :integer
# state :string(255)
# merge_status :string(255)
# id :integer not null, primary key
# target_branch :string(255) not null
# source_branch :string(255) not null
# source_project_id :integer not null
# author_id :integer
# assignee_id :integer
# title :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# st_commits :text(2147483647)
# st_diffs :text(2147483647)
# milestone_id :integer
# state :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("lib/static_model")
class MergeRequest < ActiveRecord::Base
include Issuable
include InternalId
belongs_to :target_project, foreign_key: :target_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
......@@ -254,6 +254,20 @@ class MergeRequest < ActiveRecord::Base
target_project
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
def dump_commits(commits)
......
......@@ -10,9 +10,12 @@
# created_at :datetime not null
# updated_at :datetime not null
# state :string(255)
# iid :integer
#
class Milestone < ActiveRecord::Base
include InternalId
attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
attr_accessor :author_id_of_changes
......
......@@ -4,15 +4,13 @@ module Network
class Commit
include ActionView::Helpers::TagHelper
attr_reader :refs
attr_accessor :time, :spaces, :parent_spaces
def initialize(raw_commit, refs)
@commit = Gitlab::Git::Commit.new(raw_commit)
def initialize(raw_commit)
@commit = raw_commit
@time = -1
@spaces = []
@parent_spaces = []
@refs = refs || []
end
def method_missing(m, *args, &block)
......
require "grit"
module Network
class Graph
attr_reader :days, :commits, :map, :notes
attr_reader :days, :commits, :map, :notes, :repo
def self.max_count
@max_count ||= 650
......@@ -13,7 +11,7 @@ module Network
@ref = ref
@commit = commit
@filter_ref = filter_ref
@repo = project.repo
@repo = project.repository
@commits = collect_commits
@days = index_commits
......@@ -33,11 +31,9 @@ module Network
# Get commits from repository
#
def collect_commits
refs_cache = build_refs_cache
find_commits(count_to_display_commit_in_center).map do |commit|
# Decorate with app/model/network/commit.rb
Network::Commit.new(commit, refs_cache[commit.id])
Network::Commit.new(commit)
end
end
......@@ -103,14 +99,13 @@ module Network
def find_commits(skip = 0)
opts = {
date_order: true,
max_count: self.class.max_count,
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
def commits_sort_by_ref
......@@ -126,15 +121,7 @@ module Network
end
def include_ref?(commit)
heads = commit.refs.select do |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)
commit.ref_names(@repo).include?(@ref)
end
def find_free_parent_spaces(commit)
......@@ -282,14 +269,5 @@ module Network
leaves.push(commit)
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
......@@ -6,13 +6,14 @@
# note :text
# noteable_type :string(255)
# author_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# project_id :integer
# attachment :string(255)
# line_code :string(255)
# commit_id :string(255)
# noteable_id :integer
# st_diff :text
#
require 'carrierwave/orm/activerecord'
......@@ -23,6 +24,7 @@ class Note < ActiveRecord::Base
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code, :commit_id
attr_mentionable :note
belongs_to :project
belongs_to :noteable, polymorphic: true
......@@ -53,15 +55,36 @@ class Note < ActiveRecord::Base
serialize :st_diff
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({
noteable: noteable,
commit_id: (noteable.sha if noteable.respond_to? :sha),
project: project,
author: author,
note: "_Status changed to #{status}_"
note: "_mentioned in #{mentioner.gfm_reference}_",
system: true
}, without_protection: true)
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
@commit_author ||=
project.users.find_by_email(noteable.author_email) ||
......@@ -190,6 +213,16 @@ class Note < ActiveRecord::Base
for_issue? || (for_merge_request? && !for_diff_line?)
end
# Mentionable override.
def gfm_reference
noteable.gfm_reference
end
# Mentionable override.
def local_reference
noteable
end
def noteable_type_name
if noteable_type.present?
noteable_type.downcase
......
......@@ -4,11 +4,11 @@
#
# id :integer not null, primary key
# title :string(255)
# content :text
# content :text(2147483647)
# author_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# file_name :string(255)
# expires_at :datetime
# 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 @@
# name :string(255)
# path :string(255)
# description :text
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# creator_id :integer
# default_branch :string(255)
# issues_enabled :boolean default(TRUE), not null
......@@ -21,6 +21,7 @@
# snippets_enabled :boolean default(TRUE), not null
# last_activity_at :datetime
# imported :boolean default(FALSE), not null
# import_url :string(255)
#
require "grit"
......@@ -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 :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy
has_one :pivotaltracker_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_from_project, through: :forked_project_link
......@@ -85,6 +87,7 @@ class Project < ActiveRecord::Base
:wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { within: 0..255 }
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
......@@ -136,10 +139,6 @@ class Project < ActiveRecord::Base
where(path: id, namespace_id: nil).last
end
end
def access_options
UsersProject.access_roles
end
end
def team
......@@ -171,11 +170,7 @@ class Project < ActiveRecord::Base
end
def to_param
if namespace
namespace.path + "/" + path
else
path
end
namespace.path + "/" + path
end
def web_url
......@@ -204,7 +199,7 @@ class Project < ActiveRecord::Base
def issue_exists?(issue_id)
if used_default_issues_tracker?
self.issues.where(id: issue_id).first.present?
self.issues.where(iid: issue_id).first.present?
else
true
end
......@@ -229,7 +224,7 @@ class Project < ActiveRecord::Base
end
def available_services_names
%w(gitlab_ci campfire hipchat)
%w(gitlab_ci campfire hipchat pivotaltracker)
end
def gitlab_ci?
......@@ -402,11 +397,6 @@ class Project < ActiveRecord::Base
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
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
def protected_branch? branch_name
protected_branches_names.include?(branch_name)
......@@ -432,7 +422,9 @@ class Project < ActiveRecord::Base
begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
gitlab_shell.rm_satellites(old_path_with_namespace)
ensure_satellite_exists
send_move_instructions
reset_events_cache
rescue
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
......@@ -444,4 +436,19 @@ class Project < ActiveRecord::Base
raise Exception.new('repository cannot be renamed')
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
......@@ -5,8 +5,8 @@
# id :integer not null, primary key
# url :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# type :string(255) default("ProjectHook")
# service_id :integer
#
......
......@@ -4,11 +4,11 @@
#
# id :integer not null, primary key
# title :string(255)
# content :text
# content :text(2147483647)
# author_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
......
......@@ -32,7 +32,15 @@ class ProjectTeam
end
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
def add_user(user, access)
......
......@@ -18,6 +18,7 @@ class Repository
end
def commit(id = nil)
return nil unless raw_repository
commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit
commit
......
......@@ -5,8 +5,8 @@
# id :integer not null, primary key
# url :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# type :string(255) default("ProjectHook")
# service_id :integer
#
......
......@@ -4,11 +4,11 @@
#
# id :integer not null, primary key
# title :string(255)
# content :text
# content :text(2147483647)
# author_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
......
......@@ -5,8 +5,8 @@
# id :integer not null, primary key
# url :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# type :string(255) default("ProjectHook")
# service_id :integer
#
......
......@@ -4,7 +4,7 @@
#
# id :integer not null, primary key
# 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_sent_at :datetime
# remember_created_at :datetime
......@@ -13,8 +13,8 @@
# last_sign_in_at :datetime
# current_sign_in_ip :string(255)
# last_sign_in_ip :string(255)
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# name :string(255)
# admin :boolean default(FALSE), not null
# projects_limit :integer default(10)
......@@ -47,7 +47,7 @@ class User < ActiveRecord::Base
:extern_uid, :provider, :password_expires_at,
as: [:default, :admin]
attr_accessible :projects_limit, :can_create_team, :can_create_group,
attr_accessible :projects_limit, :can_create_group,
as: :admin
attr_accessor :force_random_password
......@@ -126,6 +126,17 @@ class User < ActiveRecord::Base
after_transition any => :blocked do |user, transition|
# Remove user from all projects and
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
end
end
......@@ -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_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 :ldap, -> { where(provider: 'ldap') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
......@@ -175,24 +187,32 @@ class User < ActiveRecord::Base
end
end
def create_from_omniauth(auth, ldap = false)
gitlab_auth.create_from_omniauth(auth, ldap)
def search query
where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
end
def find_or_new_for_omniauth(auth)
gitlab_auth.find_or_new_for_omniauth(auth)
def by_username_or_id(name_or_id)
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)
def build_user(attrs = {}, options= {})
if options[:as] == :admin
User.new(defaults.merge(attrs.symbolize_keys), options)
else
User.new(attrs, options).with_defaults
end
end
def gitlab_auth
Gitlab::Auth.new
end
def search query
where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
def defaults
{
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
......@@ -204,15 +224,6 @@ class User < ActiveRecord::Base
username
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
@notification ||= Notification.new(self)
end
......@@ -323,7 +334,7 @@ class User < ActiveRecord::Base
end
def several_namespaces?
namespaces.many?
namespaces.many? || owned_groups.any?
end
def namespace_id
......@@ -374,4 +385,18 @@ class User < ActiveRecord::Base
def requires_ldap_check?
!last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
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
......@@ -2,31 +2,21 @@
#
# Table name: users_groups
#
# id :integer not null, primary key
# group_access :integer not null
# group_id :integer not null
# user_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# id :integer not null, primary key
# group_access :integer not null
# group_id :integer not null
# user_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# notification_level :integer default(3), not null
#
class UsersGroup < ActiveRecord::Base
include Notifiable
GUEST = 10
REPORTER = 20
DEVELOPER = 30
MASTER = 40
OWNER = 50
include Gitlab::Access
def self.group_access_roles
{
"Guest" => GUEST,
"Reporter" => REPORTER,
"Developer" => DEVELOPER,
"Master" => MASTER,
"Owner" => OWNER
}
Gitlab::Access.options_with_owner
end
attr_accessible :group_access, :user_id
......@@ -50,7 +40,7 @@ class UsersGroup < ActiveRecord::Base
delegate :name, :username, :email, to: :user, prefix: true
def human_access
UsersGroup.group_access_roles.key(self.group_access)
def access_field
group_access
end
end
......@@ -5,8 +5,8 @@
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# project_access :integer default(0), not null
# notification_level :integer default(3), not null
#
......@@ -14,11 +14,7 @@
class UsersProject < ActiveRecord::Base
include Gitlab::ShellAdapter
include Notifiable
GUEST = 10
REPORTER = 20
DEVELOPER = 30
MASTER = 40
include Gitlab::Access
attr_accessible :user, :user_id, :project_access
......@@ -27,7 +23,7 @@ class UsersProject < ActiveRecord::Base
validates :user, presence: true
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
delegate :name, :username, :email, to: :user, prefix: true
......@@ -103,27 +99,19 @@ class UsersProject < ActiveRecord::Base
end
def roles_hash
{
guest: GUEST,
reporter: REPORTER,
developer: DEVELOPER,
master: MASTER
}
Gitlab::Access.sym_options
end
def access_roles
{
"Guest" => GUEST,
"Reporter" => REPORTER,
"Developer" => DEVELOPER,
"Master" => MASTER
}
Gitlab::Access.options
end
end
def project_access_human
Project.access_options.key(self.project_access)
def access_field
project_access
end
alias_method :human_access, :project_access_human
def owner?
project.owner == user
end
end
......@@ -5,8 +5,8 @@
# id :integer not null, primary key
# url :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# type :string(255) default("ProjectHook")
# service_id :integer
#
......
......@@ -5,8 +5,8 @@ class ActivityObserver < BaseObserver
event_author_id = record.author_id
if record.kind_of?(Note)
# Skip system status notes like 'status changed to close'
return true if record.note.include?("_Status changed to ")
# Skip system notes, like status changes and cross-references.
return true if record.system?
# Skip wall notes to prevent spamming of dashboard
return true if record.noteable_type.blank?
......
......@@ -10,4 +10,8 @@ class BaseObserver < ActiveRecord::Observer
def current_user
Thread.current[:current_user]
end
def current_commit
Thread.current[:current_commit]
end
end
class IssueObserver < BaseObserver
def after_create(issue)
notification.new_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
end
def after_close(issue, transition)
......@@ -17,12 +19,14 @@ class IssueObserver < BaseObserver
if issue.is_being_reassigned?
notification.reassigned_issue(issue, current_user)
end
issue.notice_added_references(issue.project, current_user)
end
protected
# Create issue note with service comment like 'Status changed to closed'
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
......@@ -7,11 +7,13 @@ class MergeRequestObserver < ActivityObserver
end
notification.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user)
end
def after_close(merge_request, transition)
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)
end
......@@ -33,11 +35,13 @@ class MergeRequestObserver < ActivityObserver
def after_reopen(merge_request, transition)
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
def after_update(merge_request)
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
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.
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