Commit 6373c84d authored by Jan-Willem van der Meer's avatar Jan-Willem van der Meer

Merge remote-tracking branch 'origin/master' into feature-multi-ldap-servers

Conflicts:
	app/models/user.rb
	config/gitlab.yml.example
	spec/lib/gitlab/ldap/access_spec.rb
parents d6cabd27 37169e54
v 7.4.0
- Refactored membership logic
- Improve error reporting on users API (Julien Bianchi)
- Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally
- Default branch is protected by default
- Increase unicorn timeout to 60 seconds
- Sort search autocomplete projects by stars count so most popular go first
- Add README to tab on project show page
- Do not delete tmp/repositories itself during clean-up, only its contents
- Support for backup uploads to remote storage
- Prevent notes polling when there are not notes
- API: Add support for forking a project via the API (Bernhard Kaindl)
- API: filter project issues by milestone (Julien Bianchi)
- Fail harder in the backup script
- Zen mode for wiki and milestones (Robert Schilling)
- Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
- Font Awesome 4.2 integration (Sullivan Senechal)
v 7.3.2
- Fix creating new file via web editor
- Use gitlab-shell v2.0.1
v 7.3.1
- Fix ref parsing in Gitlab::GitAccess
- Fix error 500 when viewing diff on a file with changed permissions
- Fix adding comments to MR when source branch is master
- Fix error 500 when searching description contains relative link
v 7.3.0
- Always set the 'origin' remote in satellite actions
- Write authorized_keys in tmp/ during tests
......
......@@ -61,7 +61,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Fork the project on GitLab Cloud
1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. Add your changes to the [CHANGELOG](CHANGELOG) insert your line at a [random point](doc/workflow/gitlab_flow.md#do-not-order-commits-with-rebase) in the current version
1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
......
......@@ -31,7 +31,7 @@ gem 'omniauth-shibboleth'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 6.0'
gem "gitlab_git", '7.0.0.rc8'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
......@@ -71,8 +71,8 @@ gem "carrierwave"
gem 'dropzonejs-rails'
# for aws storage
gem "fog", "~> 1.14", group: :aws
gem "unf", group: :aws
gem "fog", "~> 1.14"
gem "unf"
# Authorization
gem "six"
......@@ -80,6 +80,9 @@ gem "six"
# Seed data
gem "seed-fu"
# Markup pipeline for GitLab
gem 'html-pipeline-gitlab', '~> 0.1.0'
# Markdown to HTML
gem "github-markup"
......@@ -87,7 +90,7 @@ gem "github-markup"
gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
gem 'org-ruby'
gem 'org-ruby', '= 0.9.9'
gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4'
......@@ -158,7 +161,7 @@ gem "rack-attack"
# Ace editor
gem 'ace-rails-ap'
# Keyboard shortcuts
# Keyboard shortcuts
gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar
......@@ -178,7 +181,7 @@ gem "jquery-ui-rails"
gem "jquery-scrollto-rails"
gem "raphael-rails", "~> 2.1.2"
gem 'bootstrap-sass', '~> 3.0'
gem "font-awesome-rails", '~> 3.2'
gem "font-awesome-rails", '~> 4.2'
gem "gitlab_emoji", "~> 0.0.1.1"
gem "gon", '~> 5.0.0'
gem 'nprogress-rails'
......
......@@ -106,7 +106,7 @@ GEM
devise (~> 3.2)
diff-lcs (1.2.5)
diffy (3.0.3)
docile (1.1.1)
docile (1.1.5)
dotenv (0.9.0)
dropzonejs-rails (0.4.14)
rails (> 3.1)
......@@ -152,7 +152,7 @@ GEM
net-ssh (>= 2.1.3)
fog-json (1.0.0)
multi_json (~> 1.0)
font-awesome-rails (3.2.1.3)
font-awesome-rails (4.2.0.0)
railties (>= 3.2, < 5.0)
foreman (0.63.0)
dotenv (>= 0.7)
......@@ -179,10 +179,9 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1)
gitlab_git (6.2.1)
gitlab_git (7.0.0.rc8)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-grit (~> 2.6)
gitlab-linguist (~> 3.0)
rugged (~> 0.21.0)
gitlab_meta (7.0)
......@@ -239,6 +238,12 @@ GEM
hipchat (0.14.0)
httparty
httparty
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
html-pipeline-gitlab (0.1.0)
gitlab_emoji (~> 0.0.1.1)
html-pipeline (~> 1.11.0)
http_parser.rb (0.5.3)
httparty (0.13.0)
json (~> 1.8)
......@@ -327,7 +332,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
org-ruby (0.9.8)
org-ruby (0.9.9)
rubypants (~> 0.2)
orm_adapter (0.5.0)
pg (0.15.1)
......@@ -471,7 +476,7 @@ GEM
redis (>= 3.0.4)
redis-namespace (>= 1.3.1)
simple_oauth (0.1.9)
simplecov (0.8.2)
simplecov (0.9.0)
docile (~> 1.1.0)
multi_json
simplecov-html (~> 0.8.0)
......@@ -609,7 +614,7 @@ DEPENDENCIES
factory_girl_rails
ffaker
fog (~> 1.14)
font-awesome-rails (~> 3.2)
font-awesome-rails (~> 4.2)
foreman
gemnasium-gitlab-service (~> 0.2)
github-markup
......@@ -617,7 +622,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1)
gitlab_git (~> 6.0)
gitlab_git (= 7.0.0.rc8)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.1.0)
gollum-lib (~> 3.0.0)
......@@ -629,6 +634,7 @@ DEPENDENCIES
guard-spinach
haml-rails
hipchat (~> 0.14.0)
html-pipeline-gitlab (~> 0.1.0)
httparty
jasmine (= 2.0.2)
jquery-atwho-rails (~> 0.3.3)
......@@ -650,7 +656,7 @@ DEPENDENCIES
omniauth-google-oauth2
omniauth-shibboleth
omniauth-twitter
org-ruby
org-ruby (= 0.9.9)
pg
poltergeist (~> 1.5.1)
pry
......
......@@ -23,7 +23,7 @@
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
- [![PullReview stats](https://www.pullreview.com/gitlab/gitlab-org/gitlab-ce/badges/master.svg?)](https://www.pullreview.com/gitlab.gitlab.com/gitlab-org/gitlab-ce/reviews/master)
......@@ -38,17 +38,6 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Third-party applications
Access GitLab from multiple platforms with applications below.
These applications are maintained by contributors, GitLab B.V. does not offer support for them.
- [iPhone app](http://gitlabcontrol.com/)
- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
- [Command line client](https://github.com/drewblessing/gitlab-cli)
- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
## Requirements
- Ubuntu/Debian/CentOS/RHEL**
......@@ -61,7 +50,19 @@ These applications are maintained by contributors, GitLab B.V. does not offer su
## Installation
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
Since a manual installation is a lot of work and error prone we strongly recommend fast and reliable Omnibus package installation (deb/rpm) on that page.
## Third-party applications
Access GitLab from multiple platforms with applications below.
These applications are maintained by contributors, GitLab B.V. does not offer support for them.
- [iPhone app](http://gitlabcontrol.com/)
- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
- [Command line client](https://github.com/drewblessing/gitlab-cli)
- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
### New versions
......
......@@ -46,10 +46,10 @@ class Admin
modal.hide()
$('.change-owner-link').show()
$('li.users_project').bind 'ajax:success', ->
$('li.project_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
$('li.users_group').bind 'ajax:success', ->
$('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
@Admin = Admin
......@@ -38,7 +38,7 @@
# Return users list. Filtered by query
# Only active users retrieved
users: (query, callback) ->
users: (query, skip_ldap, callback) ->
url = Api.buildUrl(Api.users_path)
$.ajax(
......@@ -48,6 +48,7 @@
search: query
per_page: 20
active: true
skip_ldap: skip_ldap
dataType: "json"
).done (users) ->
callback(users)
......
......@@ -15,13 +15,14 @@
#= require jquery.atwho
#= require jquery.scrollTo
#= require jquery.blockUI
#= require turbolinks
#= require jquery.turbolinks
#= require turbolinks
#= require bootstrap
#= require select2
#= require raphael
#= require g.raphael-min
#= require g.bar-min
#= require chart-lib.min
#= require branch-graph
#= require highlight.pack
#= require ace/ace
......@@ -149,7 +150,6 @@ $ ->
if (flash = $(".flash-container")).length > 0
flash.click -> $(@).fadeOut()
flash.show()
setTimeout (-> flash.fadeOut()), 5000
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
......@@ -172,8 +172,8 @@ $ ->
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
$(@).find('i').
toggleClass('icon-chevron-down').
toggleClass('icon-chevron-up')
toggleClass('fa fa-chevron-down').
toggleClass('fa fa-chevron-up')
$(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault()
......
......@@ -8,7 +8,7 @@ $ ->
#
$("body").on "click", ".js-toggle-button", (e) ->
$(@).find('i').
toggleClass('icon-chevron-down').
toggleClass('icon-chevron-up')
toggleClass('fa fa-chevron-down').
toggleClass('fa fa-chevron-up')
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
e.preventDefault()
......@@ -90,11 +90,15 @@ class @BranchGraph
renderPartialGraph: ->
start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
start = 0 if start < 0
if start < 0
isGraphEdge = true
start = 0
end = start + 40
end = @commits.length if @commits.length < end
if @commits.length < end
isGraphEdge = true
end = @commits.length
if @prev_start == -1 or Math.abs(@prev_start - start) > 10
if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge
i = start
@prev_start = start
......
@Chart =
labels: []
values: []
init: (labels, values, title) ->
r = Raphael('activity-chart')
fin = ->
@flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this)
fout = ->
@flag.animate
opacity: 0, 300, -> @remove()
r.text(160, 10, title).attr font: "13px sans-serif"
r.barchart(
10, 20, 560, 200,
[values],
{colors:["#456"]}
).label(labels, true)
.hover(fin, fout)
......@@ -27,6 +27,8 @@ class Dispatcher
new ZenMode()
when 'projects:milestones:show'
new Milestone()
when 'projects:milestones:new'
new ZenMode()
when 'projects:issues:new','projects:issues:edit'
GitLab.GfmAutoComplete.setup()
shortcut_handler = new ShortcutsNavigation()
......@@ -87,6 +89,7 @@ class Dispatcher
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'snippets', 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
......
......@@ -10,6 +10,5 @@ class Flash
flash.click -> $(@).fadeOut()
flash.show()
setTimeout (-> flash.fadeOut()), 5000
@Flash = Flash
class GroupMembers
constructor: ->
$('li.users_group').bind 'ajax:success', ->
$('li.group_member').bind 'ajax:success', ->
$(this).fadeOut()
@GroupMembers = GroupMembers
......
......@@ -7,18 +7,18 @@ $(document).ready ->
divHover = "<div class=\"div-dropzone-hover\"></div>"
divSpinner = "<div class=\"div-dropzone-spinner\"></div>"
divAlert = "<div class=\"" + alertClass + "\"></div>"
iconPicture = "<i class=\"icon-picture div-dropzone-icon\"></i>"
iconSpinner = "<i class=\"icon-spinner icon-spin div-dropzone-icon\"></i>"
iconPicture = "<i class=\"fa fa-picture-o div-dropzone-icon\"></i>"
iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_image_path_upload = window.project_image_path_upload or null
$("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>"
$("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>"
$(".div-dropzone").parent().addClass "div-dropzone-wrapper"
$(".div-dropzone").append divHover
$(".div-dropzone-hover").append iconPicture
$(".div-dropzone").append divSpinner
$(".div-dropzone").append divSpinner
$(".div-dropzone-spinner").append iconSpinner
$(".div-dropzone-spinner").css
"opacity": 0
......@@ -27,12 +27,12 @@ $(document).ready ->
dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload
dictDefaultMessage: ""
clickable: false
clickable: true
paramName: "markdown_img"
maxFilesize: 10
uploadMultiple: false
acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png"
headers:
headers:
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
previewContainer: false
......@@ -91,7 +91,7 @@ $(document).ready ->
handlePaste = (e) ->
e.preventDefault()
my_event = e.originalEvent
if my_event.clipboardData and my_event.clipboardData.items
processItem(my_event)
......@@ -115,7 +115,7 @@ $(document).ready ->
return item
i++
return false
pasteText = (text) ->
caretStart = $(child)[0].selectionStart
caretEnd = $(child)[0].selectionEnd
......@@ -126,12 +126,12 @@ $(document).ready ->
$(child).val beforeSelection + text + afterSelection
$(".markdown-area").trigger "input"
getFilename = (e) ->
getFilename = (e) ->
if window.clipboardData and window.clipboardData.getData
value = window.clipboardData.getData("Text")
else if e.clipboardData and e.clipboardData.getData
value = e.clipboardData.getData("text/plain")
value = value.split("\r")
value.first()
......@@ -154,7 +154,7 @@ $(document).ready ->
success: (e, textStatus, response) ->
insertToTextArea(filename, formatLink(response.responseJSON.link))
error: (response) ->
showError(response.responseJSON.message)
......@@ -190,7 +190,7 @@ $(document).ready ->
$(".markdown-selector").click (e) ->
e.preventDefault()
$(@).closest(".div-dropzone-wrapper").find(".div-dropzone").click()
$(@).closest('.gfm-form').find('.div-dropzone').click()
return
return
......@@ -15,7 +15,7 @@ class MergeRequest
modal = $('#modal_merge_info').modal(show: false)
disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request'
disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
# Local jQuery finder
......@@ -40,6 +40,8 @@ class MergeRequest
if @opts.ci_enable
$.get @opts.url_to_ci_check, (data) =>
this.showCiState data.status
if data.coverage
this.showCiCoverage data.coverage
, 'json'
bindEvents: ->
......@@ -102,7 +104,11 @@ class MergeRequest
when "running", "pending"
$('.mr-state-widget').addClass("panel-warning")
showCiCoverage: (coverage) ->
cov_html = $('<span>')
cov_html.addClass('ci-coverage')
cov_html.text('Coverage ' + coverage + '%')
$('.ci_widget:visible').append(cov_html)
loadDiff: (event) ->
$.ajax
......
......@@ -6,6 +6,7 @@ class Notes
@notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root?
@note_ids = note_ids
@last_fetched_at = last_fetched_at
@noteable_url = document.URL
@initRefresh()
@setupMainTargetNoteForm()
@cleanBinding()
......@@ -95,7 +96,8 @@ class Notes
, 15000
refresh: ->
@getContent() unless document.hidden
unless document.hidden or (@noteable_url != document.URL)
@getContent()
getContent: ->
$.ajax
......
......@@ -59,3 +59,12 @@ $ ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href")
defaultView = $.cookie("default_view")
if defaultView
$("a[href=" + defaultView + "]").tab "show"
else
$("a[data-toggle='tab']:first").tab "show"
......@@ -24,22 +24,7 @@ class window.ContributorsStatGraph
class: 'graph-author-commits-count'
})
commits.text(author.commits + " commits")
additions = $('<span/>', {
class: 'graph-additions'
})
additions.text(author.additions + " ++")
deletions = $('<span/>', {
class: 'graph-deletions'
})
deletions.text(author.deletions + " --")
$('<span/>').append(commits)
.append(" / ")
.append(additions)
.append(" / ")
.append(deletions)
create_author_header: (author) ->
list_item = $('<li/>', {
......
......@@ -90,4 +90,4 @@ window.ContributorsStatGraphUtil =
true
else
false
......@@ -15,12 +15,14 @@ $ ->
user.name
$('.ajax-users-select').each (i, select) ->
skip_ldap = $(select).hasClass('skip_ldap')
$(select).select2
placeholder: "Search for a user"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) ->
Api.users query.term, (users) ->
Api.users query.term, skip_ldap, (users) ->
data = { results: users }
query.callback(data)
......
......@@ -32,6 +32,8 @@ class @ZenMode
@active_zen_area = @active_checkbox.parent().find('textarea')
@active_zen_area.focus()
window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id')
# Disable dropzone in ZEN mode
Dropzone.forElement('.div-dropzone').disable()
exitZenMode: =>
if @active_zen_area isnt null
......@@ -41,6 +43,8 @@ class @ZenMode
@active_checkbox = null
window.location.hash = ''
window.scrollTo(window.pageXOffset, @scroll_position)
# Enable dropzone when leaving ZEN mode
Dropzone.forElement('.div-dropzone').enable()
checkboxFromLocationHash: (e) ->
id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, ''))
......
......@@ -60,7 +60,6 @@
.highlight {
margin-bottom: 9px;
@include border-radius(4px);
> pre {
margin: 0;
......
.flash-container {
display: none;
cursor: pointer;
margin: 0;
text-align: center;
color: #fff;
font-size: 14px;
position: fixed;
bottom: 0;
width: 100%;
opacity: 0.8;
z-index: 100;
.flash-notice {
background: #49C;
padding: 10px;
text-shadow: 0 1px 1px #178;
@extend .alert;
@extend .alert-info;
}
.flash-alert {
background: #C67;
text-shadow: 0 1px 1px #945;
padding: 10px;
@extend .alert;
@extend .alert-danger;
}
}
textarea {
resize: vertical;
}
input[type='search'].search-text-input {
background-image: image-url("icon-search.png");
background-repeat: no-repeat;
......
/**
* Styles that apply to all GFM related forms.
*/
.issue-form, .merge-request-form, .wiki-form {
.description {
height: 20em;
}
}
.wiki-form {
.description {
height: 26em;
}
}
.milestone-form {
.description {
height: 14em;
}
}
\ No newline at end of file
/**
* Styles that apply to both issues and merge requests.
*/
.issue-form, .merge-request-form {
.description {
height: 20em;
}
}
......@@ -8,43 +8,51 @@
*/
.issue-box {
color: #666;
color: #555;
margin:20px 0;
background: #FFF;
border: 1px solid #EEE;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
background: #f9f9f9;
border-top-left-radius: 5px;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
&.issue-box-closed {
border-color: $border_danger;
.state {
background-color: #F3CECE;
border-color: $border_danger;
}
.state-label {
background-color: $bg_danger;
color: #FFF;
border-color: $border_danger;
}
}
&.issue-box-merged {
border-color: $border_primary;
.state {
background-color: #B7CEE7;
border-color: $border_primary;
}
.state-label {
background-color: $bg_primary;
color: #FFF;
border-color: $border_primary;
}
}
&.issue-box-open {
border-color: $border_success;
.state {
border-color: $border_success;
background-color: #D6F1D7;
border-color: $bg_success;
}
.state-label {
background-color: $bg_success;
color: #FFF;
}
}
&.issue-box-expired {
border-color: #cea61b;
.state {
background-color: #EEE9B3;
border-color: #faebcc;
}
.state-label {
background: #cea61b;
color: #FFF;
}
......@@ -55,8 +63,7 @@
}
.state {
border-bottom: 1px solid #DDD;
padding: 10px 15px;
background-color: #f9f9f9;
}
.title {
......@@ -104,12 +111,14 @@
font-size: 14px;
float: left;
font-weight: bold;
padding: 10px 15px;
border-top-left-radius: 5px;
}
.creator {
float: right;
padding: 10px 15px;
a {
color: #FFF;
text-decoration: underline;
}
}
......
......@@ -124,7 +124,7 @@ $list-group-active-bg: $bg_primary;
color: #888;
text-shadow: 0 1px 1px #fff;
}
i[class^="icon-"] {
i[class~="fa"] {
line-height: 14px;
}
}
......
.white {
background-color: #fff;
.line.hll {
background: #FFA;
}
.highlight{
border-left: 1px solid #eee;
}
pre {
background-color: #fff;
color: #333;
......@@ -179,8 +173,16 @@
@include box-shadow(0 5px 15px #000);
}
.wiki, .note-body {
.highlight {
border: 1px solid #DDD;
.file-content {
&.code .white {
.highlight {
border-left: 1px solid #eee;
}
}
&.wiki .white {
.highlight, pre, .hljs {
background: #F9F9F9;
}
}
}
......@@ -244,7 +244,6 @@ li.commit {
font-family: inherit;
padding-left: $left;
position: relative;
resize: vertical;
z-index: 2;
}
}
......@@ -113,6 +113,10 @@
margin-bottom: 0;
}
}
.ci-coverage {
float: right;
}
}
.merge-request-show-labels .label {
......
......@@ -19,6 +19,10 @@ ul.notes {
@extend .cgray;
padding-bottom: 15px;
a:hover {
text-decoration: none;
}
.avatar {
float: left;
margin-right: 10px;
......@@ -115,8 +119,7 @@ ul.notes {
display: none;
float: right;
[class^="icon-"],
[class*="icon-"] {
[class~="fa"] {
font-size: 16px;
line-height: 16px;
vertical-align: middle;
......@@ -143,8 +146,14 @@ ul.notes {
*/
.diff-file tr.line_holder {
@mixin show-add-diff-note {
filter: alpha(opacity=100);
opacity: 1.0;
}
.add-diff-note {
background: image-url("diff_note_add.png") no-repeat left 0;
border: none;
height: 22px;
margin-left: -65px;
position: absolute;
......@@ -156,8 +165,7 @@ ul.notes {
filter: alpha(opacity=0);
&:hover {
opacity: 1.0;
filter: alpha(opacity=100);
@include show-add-diff-note;
}
}
......@@ -166,8 +174,7 @@ ul.notes {
background: $hover !important;
.add-diff-note {
opacity: 1.0;
filter: alpha(opacity=100);
@include show-add-diff-note;
}
}
}
......
......@@ -48,6 +48,10 @@
float: right;
margin-left: 20px;
a:hover {
text-decoration: none;
}
.count {
margin-left: 5px;
}
......
......@@ -8,7 +8,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def show
@members = @group.members.order("group_access DESC").page(params[:members_page]).per(30)
@members = @group.members.order("access_level DESC").page(params[:members_page]).per(30)
@projects = @group.projects.page(params[:projects_page]).per(30)
end
......@@ -40,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def project_teams_update
@group.add_users(params[:user_ids].split(','), params[:group_access])
@group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
......
......@@ -16,10 +16,10 @@ class Admin::ProjectsController < Admin::ApplicationController
def show
if @group
@group_members = @group.members.order("group_access DESC").page(params[:group_members_page]).per(30)
@group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(30)
end
@project_members = @project.users_projects.page(params[:project_members_page]).per(30)
@project_members = @project.project_members.page(params[:project_members_page]).per(30)
end
def transfer
......
......@@ -62,7 +62,7 @@ class ApplicationController < ActionController::Base
end
end
def after_sign_in_path_for resource
def after_sign_in_path_for(resource)
if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
sign_out resource
flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
......
class ConfirmationsController < Devise::ConfirmationsController
protected
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
signed_in_root_path(resource)
else
sign_in(resource)
if signed_in?(resource_name)
signed_in_root_path(resource)
else
new_session_path(resource_name)
end
end
end
end
class UsersGroupsController < ApplicationController
class Groups::GroupMembersController < ApplicationController
before_filter :group
# Authorize
......@@ -7,18 +7,18 @@ class UsersGroupsController < ApplicationController
layout 'group'
def create
@group.add_users(params[:user_ids].split(','), params[:group_access])
@group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to members_group_path(@group), notice: 'Users were successfully added.'
end
def update
@member = @group.users_groups.find(params[:id])
@member = @group.group_members.find(params[:id])
@member.update_attributes(member_params)
end
def destroy
@users_group = @group.users_groups.find(params[:id])
@users_group = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy
respond_to do |format|
......@@ -43,6 +43,6 @@ class UsersGroupsController < ApplicationController
end
def member_params
params.require(:users_group).permit(:group_access, :user_id)
params.require(:group_member).permit(:access_level, :user_id)
end
end
......@@ -67,15 +67,15 @@ class GroupsController < ApplicationController
def members
@project = group.projects.find(params[:project_id]) if params[:project_id]
@members = group.users_groups
@members = group.group_members
if params[:search].present?
users = group.users.search(params[:search]).to_a
@members = @members.where(user_id: users)
end
@members = @members.order('group_access DESC').page(params[:page]).per(50)
@users_group = UsersGroup.new
@members = @members.order('access_level DESC').page(params[:page]).per(50)
@users_group = GroupMember.new
end
def edit
......
......@@ -2,11 +2,11 @@ class Profiles::GroupsController < ApplicationController
layout "profile"
def index
@user_groups = current_user.users_groups.page(params[:page]).per(20)
@user_groups = current_user.group_members.page(params[:page]).per(20)
end
def leave
@users_group = group.users_groups.where(user_id: current_user.id).first
@users_group = group.group_members.where(user_id: current_user.id).first
if can?(current_user, :destroy, @users_group)
@users_group.destroy
redirect_to(profile_groups_path, info: "You left #{group.name} group.")
......
......@@ -3,8 +3,8 @@ class Profiles::NotificationsController < ApplicationController
def show
@notification = current_user.notification
@users_projects = current_user.users_projects
@users_groups = current_user.users_groups
@project_members = current_user.project_members
@group_members = current_user.group_members
end
def update
......@@ -14,13 +14,13 @@ class Profiles::NotificationsController < ApplicationController
current_user.notification_level = params[:notification_level]
current_user.save
elsif type == 'group'
users_group = current_user.users_groups.find(params[:notification_id])
users_group = current_user.group_members.find(params[:notification_id])
users_group.notification_level = params[:notification_level]
users_group.save
else
users_project = current_user.users_projects.find(params[:notification_id])
users_project.notification_level = params[:notification_level]
users_project.save
project_member = current_user.project_members.find(params[:notification_id])
project_member.notification_level = params[:notification_level]
project_member.save
end
end
end
......@@ -17,10 +17,8 @@ class Projects::BranchesController < Projects::ApplicationController
end
def create
result = CreateBranchService.new.execute(project,
params[:branch_name],
params[:ref],
current_user)
result = CreateBranchService.new(project, current_user).
execute(params[:branch_name], params[:ref])
if result[:status] == :success
@branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name)
......@@ -31,7 +29,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
def destroy
DeleteBranchService.new.execute(project, params[:id], current_user)
DeleteBranchService.new(project, current_user).execute(params[:id])
@branch_name = params[:id]
respond_to do |format|
......
......@@ -12,20 +12,8 @@ class Projects::CommitController < Projects::ApplicationController
return git_not_found! unless @commit
@line_notes = project.notes.for_commit_id(commit.id).inline
@branches = begin
project.repository.branch_names_contains(commit.id)
rescue Grit::Git::GitTimeout
[]
end
begin
@diffs = @commit.diffs
rescue Grit::Git::GitTimeout
@diffs = []
@diff_timeout = true
end
@branches = project.repository.branch_names_contains(commit.id)
@diffs = @commit.diffs
@note = project.build_commit_note(commit)
@notes_count = project.notes.for_commit_id(commit.id).count
@notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
......
......@@ -10,7 +10,8 @@ class Projects::EditTreeController < Projects::BaseTreeController
end
def update
result = Files::UpdateService.new(@project, current_user, params, @ref, @path).execute
result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
......
......@@ -7,19 +7,34 @@ class Projects::GraphsController < Projects::ApplicationController
def show
respond_to do |format|
format.html
format.js do
format.json do
fetch_graph
end
end
end
def commits
@commits = @project.repository.commits(nil, nil, 2000, 0, true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
@commits_per_month = @commits_graph.commits_per_month
end
private
def fetch_graph
@log = @project.repository.graph_log.to_json
@success = true
rescue => ex
@commits = @project.repository.commits(nil, nil, 6000, 0, true)
@log = []
@success = false
@commits.each do |commit|
@log << {
author_name: commit.author_name.force_encoding('UTF-8'),
author_email: commit.author_email.force_encoding('UTF-8'),
date: commit.committed_date.strftime("%Y-%m-%d")
}
end
render json: @log.to_json
end
end
......@@ -28,7 +28,7 @@ class Projects::IssuesController < Projects::ApplicationController
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
sort_param = params[:sort] || 'newest'
@sort = sort_param.humanize unless sort_param.empty?
@assignees = User.where(id: @project.issues.pluck(:assignee_id))
@assignees = User.where(id: @project.issues.pluck(:assignee_id)).active
respond_to do |format|
format.html
......
......@@ -122,7 +122,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.open? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch]
@merge_request.automerge!(current_user, params[:merge_commit_message])
@merge_request.automerge!(current_user, params[:commit_message])
@status = true
else
@status = false
......@@ -143,7 +143,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def update_branches
@target_project = selected_target_project
@target_branches = @target_project.repository.branch_names
@target_branches
respond_to do |format|
format.js
......@@ -151,8 +150,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
status = @merge_request.source_project.ci_service.commit_status(merge_request.last_commit.sha)
response = {status: status}
ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha)
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha)
end
response = {
status: status,
coverage: coverage
}
render json: response
end
......
......@@ -13,7 +13,7 @@ class Projects::NewTreeController < Projects::BaseTreeController
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path))
else
flash[:alert] = result[:error]
flash[:alert] = result[:message]
render :show
end
end
......
......@@ -4,11 +4,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
before_filter :authorize_code_access!
before_filter :require_non_empty_project
def stats
@stats = Gitlab::Git::Stats.new(@repository.raw, @repository.root_ref)
@graph = @stats.graph
end
def archive
unless can?(current_user, :download_code, @project)
render_404 and return
......
......@@ -13,9 +13,8 @@ class Projects::TagsController < Projects::ApplicationController
end
def create
result = CreateTagService.new.execute(@project, params[:tag_name],
params[:ref], params[:message],
current_user)
result = CreateTagService.new(@project, current_user).
execute(params[:tag_name], params[:ref], params[:message])
if result[:status] == :success
@tag = result[:tag]
redirect_to project_tags_path(@project)
......
......@@ -6,18 +6,18 @@ class Projects::TeamMembersController < Projects::ApplicationController
def index
@group = @project.group
@users_projects = @project.users_projects.order('project_access DESC')
@project_members = @project.project_members.order('access_level DESC')
@project_group_links = @project.project_group_links
end
def new
@user_project_relation = project.users_projects.new
@user_project_relation = project.project_members.new
end
def create
users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:project_access]]
@project.team << [users, params[:access_level]]
if params[:redirect_to]
redirect_to params[:redirect_to]
......@@ -27,7 +27,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def update
@user_project_relation = project.users_projects.find_by(user_id: member)
@user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params)
unless @user_project_relation.valid?
......@@ -37,7 +37,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def destroy
@user_project_relation = project.users_projects.find_by(user_id: member)
@user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation.destroy
respond_to do |format|
......@@ -47,7 +47,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def leave
project.users_projects.find_by(user_id: current_user).destroy
project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
format.html { redirect_to :back }
......@@ -70,6 +70,6 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def member_params
params.require(:team_member).permit(:user_id, :project_access)
params.require(:project_member).permit(:user_id, :access_level)
end
end
......@@ -15,11 +15,11 @@ class RegistrationsController < Devise::RegistrationsController
super
end
def after_sign_up_path_for resource
def after_sign_up_path_for(resource)
new_user_session_path
end
def after_inactive_sign_up_path_for resource
def after_inactive_sign_up_path_for(resource)
new_user_session_path
end
......
......@@ -19,10 +19,8 @@ class ProjectsFinder
# Return ALL group projects
group.projects
else
projects_members = UsersProject.where(
project_id: group.projects,
user_id: current_user
)
projects_members = ProjectMember.in_projects(group.projects).
with_user(current_user)
if projects_members.any?
# User is a project member
......@@ -32,7 +30,11 @@ class ProjectsFinder
# internal projects
# joined projects
#
projects_ids = projects_members.pluck(:project_id)
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_members.pluck(:source_id),
Project.public_and_internal_levels
)
else
# User has no access to group or group projects
# or has access through shared project
......@@ -41,20 +43,19 @@ class ProjectsFinder
# public projects
# internal projects
# shared projects
projects_ids = []
ProjectGroupLink.where(project_id: group.projects).each do |shared_project|
if shared_project.group.users.include?(current_user) || shared_project.project.users.include?(current_user)
projects_ids << shared_project.project.id
end
end
end
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_ids,
Project.public_and_internal_levels
)
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_ids,
Project.public_and_internal_levels
)
end
end
else
# Not authenticated
......
......@@ -151,12 +151,6 @@ module ApplicationHelper
sanitize(str, tags: %w(a span))
end
def image_url(source)
# prevent relative_root_path being added twice (it's part of root_url and path_to_image)
root_url.sub(/#{root_path}$/, path_to_image(source))
end
alias_method :url_to_image, :image_url
def body_data_page
path = controller.controller_path.split('/')
......@@ -187,13 +181,6 @@ module ApplicationHelper
end
end
def first_line(str)
lines = str.split("\n")
line = lines.first
line += "..." if lines.size > 1
line
end
def broadcast_message
BroadcastMessage.current
end
......@@ -242,7 +229,7 @@ module ApplicationHelper
css_class << " hide" unless visible
content_tag :div, class: css_class do
content_tag(:i, nil, class: 'icon-spinner icon-spin') + text
content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text
end
end
......
......@@ -19,7 +19,7 @@ module EventsHelper
[event.action_name, target].join(" ")
end
def event_filter_link key, tooltip
def event_filter_link(key, tooltip)
key = key.to_s
inactive = if @event_filter.active? key
nil
......@@ -36,10 +36,10 @@ module EventsHelper
def icon_for_event
{
EventFilter.push => "icon-upload-alt",
EventFilter.merged => "icon-check",
EventFilter.comments => "icon-comments",
EventFilter.team => "icon-user",
EventFilter.push => 'fa fa-upload',
EventFilter.merged => 'fa fa-check-square-o',
EventFilter.comments => 'fa fa-comments',
EventFilter.team => 'fa fa-user',
}
end
......@@ -136,7 +136,7 @@ module EventsHelper
end
def event_note(text)
text = first_line(text)
text = first_line_in_markdown(text)
text = truncate(text, length: 150)
sanitize(markdown(text), tags: %w(a img b pre p))
end
......
......@@ -51,6 +51,14 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe
end
def first_line_in_markdown(text)
line = text.split("\n").detect do |i|
i.present? && markdown(i).present?
end
line += '...' unless line.nil?
line
end
def render_wiki_content(wiki_page)
if wiki_page.format == :markdown
markdown(wiki_page.content)
......@@ -65,7 +73,12 @@ module GitlabMarkdownHelper
paths.uniq.each do |file_path|
# If project does not have repository
# its nothing to rebuild
if @repository.exists? && !@repository.empty?
#
# TODO: pass project variable to markdown helper instead of using
# instance variable. Right now it generates invalid path for pages out
# of project scope. Example: search results where can be rendered markdown
# from different projects
if @repository && @repository.exists? && !@repository.empty?
new_path = rebuild_path(file_path)
# Finds quoted path so we don't replace other mentions of the string
# eg. "doc/api" will be replaced and "/home/doc/api/text" won't
......
module IconsHelper
def boolean_to_icon(value)
if value.to_s == "true"
content_tag :i, nil, class: 'icon-circle cgreen'
content_tag :i, nil, class: 'fa fa-circle cgreen'
else
content_tag :i, nil, class: 'icon-off clgray'
content_tag :i, nil, class: 'fa fa-power-off clgray'
end
end
def public_icon
content_tag :i, nil, class: 'icon-globe'
content_tag :i, nil, class: 'fa fa-globe'
end
def internal_icon
content_tag :i, nil, class: 'icon-shield'
content_tag :i, nil, class: 'fa fa-shield'
end
def private_icon
content_tag :i, nil, class: 'icon-lock'
content_tag :i, nil, class: 'fa fa-lock'
end
end
module IssuesHelper
def issue_css_classes issue
def issue_css_classes(issue)
classes = "issue"
classes << " closed" if issue.closed?
classes << " today" if issue.today?
......@@ -84,7 +84,7 @@ module IssuesHelper
'id', 'name', object.assignee_id)
end
def milestone_options object
def milestone_options(object)
options_from_collection_for_select(object.project.milestones.active,
'id', 'title', object.milestone_id)
end
......
......@@ -24,7 +24,7 @@ module MergeRequestsHelper
}
end
def mr_css_classes mr
def mr_css_classes(mr)
classes = "merge-request"
classes << " closed" if mr.closed?
classes << " merged" if mr.merged?
......
......@@ -52,8 +52,8 @@ module NotesHelper
discussion_id: discussion_id
}
link_to "", "javascript:;", class: "add-diff-note js-add-diff-note-button",
data: data, title: "Add a comment to this line"
button_tag '', class: 'btn add-diff-note js-add-diff-note-button',
data: data, title: 'Add a comment to this line'
end
def link_to_reply_diff(note)
......@@ -67,11 +67,10 @@ module NotesHelper
discussion_id: note.discussion_id
}
link_to "javascript:;", class: "btn reply-btn js-discussion-reply-button",
data: data, title: "Add a reply" do
link_text = ""
link_text < content_tag(:i, nil, class: 'icon-comment')
link_text << "Reply"
end
button_tag class: 'btn reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
link_text = content_tag(:i, nil, class: 'fa fa-comment')
link_text << ' Reply'
end
end
end
module NotificationsHelper
def notification_icon(notification)
if notification.disabled?
content_tag :i, nil, class: 'icon-volume-off ns-mute'
content_tag :i, nil, class: 'fa fa-volume-off ns-mute'
elsif notification.participating?
content_tag :i, nil, class: 'icon-volume-down ns-part'
content_tag :i, nil, class: 'fa fa-volume-down ns-part'
elsif notification.watch?
content_tag :i, nil, class: 'icon-volume-up ns-watch'
content_tag :i, nil, class: 'fa fa-volume-up ns-watch'
else
content_tag :i, nil, class: 'icon-circle-blank ns-default'
content_tag :i, nil, class: 'fa fa-circle-o ns-default'
end
end
end
module ProfileHelper
def oauth_active_class provider
def oauth_active_class(provider)
if current_user.provider == provider.to_s
'active'
end
......
......@@ -3,7 +3,7 @@ module ProjectsHelper
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
end
def link_to_project project
def link_to_project(project)
link_to project do
title = content_tag(:span, project.name, class: 'project-name')
......@@ -39,7 +39,7 @@ module ProjectsHelper
end
end
def project_title project
def project_title(project)
if project.group
content_tag :span do
link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
......@@ -133,7 +133,7 @@ module ProjectsHelper
'Star'
end
content_tag('i', ' ', class: 'icon-star') + toggle_text
content_tag('i', ' ', class: 'fa fa-star') + toggle_text
end
count_html = content_tag('span', class: 'count') do
......@@ -156,6 +156,14 @@ module ProjectsHelper
end
end
def link_to_toggle_fork
out = content_tag(:i, '', class: 'fa fa-code-fork')
out << ' Fork'
out << content_tag(:span, class: 'count') do
@project.forks_count.to_s
end
end
private
def get_project_nav_tabs(project, current_user)
......
......@@ -80,7 +80,8 @@ module SearchHelper
# Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5)
ProjectsFinder.new.execute(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
ProjectsFinder.new.execute(current_user).search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
url: project_path(p)
......
......@@ -2,6 +2,7 @@ module SelectsHelper
def users_select_tag(id, opts = {})
css_class = "ajax-users-select "
css_class << "multiselect " if opts[:multiple]
css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
......
......@@ -89,7 +89,7 @@ module TabHelper
end
# Use nav_tab for save controller/action but different params
def nav_tab key, value, &block
def nav_tab(key, value, &block)
o = {}
o[:class] = ""
......
module TagsHelper
def tag_path tag
def tag_path(tag)
"/tags/#{tag}"
end
def tag_list project
def tag_list(project)
html = ''
project.tag_list.each do |tag|
html += link_to tag, tag_path(tag)
......
......@@ -36,9 +36,9 @@ module TreeHelper
# type - String type of the tree item; either 'folder' or 'file'
def tree_icon(type)
icon_class = if type == 'folder'
'icon-folder-close'
'fa fa-folder'
else
'icon-file-alt'
'fa fa-file-o'
end
content_tag :i, nil, class: icon_class
......@@ -80,7 +80,7 @@ module TreeHelper
end
end
def up_dir_path tree
def up_dir_path(tree)
file = File.join(@path, "..")
tree_join(@ref, file)
end
......
module Emails
module Groups
def group_access_granted_email(user_group_id)
@membership = UsersGroup.find(user_group_id)
@membership = GroupMember.find(user_group_id)
@group = @membership.group
@target_url = group_url(@group)
mail(to: @membership.user.email,
......
module Emails
module Projects
def project_access_granted_email(user_project_id)
@users_project = UsersProject.find user_project_id
@project = @users_project.project
@project_member = ProjectMember.find user_project_id
@project = @project_member.project
@target_url = project_url(@project)
mail(to: @users_project.user.email,
mail(to: @project_member.user.email,
subject: subject("Access to project was granted"))
end
......
......@@ -14,7 +14,7 @@ class Ability
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject)
when "UsersGroup" then users_group_abilities(user, subject)
when "GroupMember" then users_group_abilities(user, subject)
else []
end.concat(global_abilities(user))
end
......@@ -184,7 +184,7 @@ class Ability
]
end
def group_abilities user, group
def group_abilities(user, group)
rules = []
if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any?
......@@ -209,7 +209,7 @@ class Ability
rules.flatten
end
def namespace_abilities user, namespace
def namespace_abilities(user, namespace)
rules = []
# Only namespace owner and administrators can manage it
......
......@@ -88,9 +88,24 @@ class Commit
description.present?
end
def hook_attrs(project)
path_with_namespace = project.path_with_namespace
{
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}",
author: {
name: author_name,
email: author_email
}
}
end
# Discover issues should be closed when this commit is pushed to a project's
# default branch.
def closes_issues project
def closes_issues(project)
Gitlab::ClosingIssueExtractor.closed_by_message_in_project(safe_message, project)
end
......@@ -108,4 +123,8 @@ class Commit
super
end
def parents
@parents ||= Commit.decorate(super)
end
end
......@@ -134,7 +134,7 @@ module Issuable
def to_hook_data
{
object_kind: self.class.name.underscore,
object_attributes: self.attributes
object_attributes: hook_attrs
}
end
......
......@@ -10,7 +10,7 @@ module Mentionable
module ClassMethods
# Indicate which attributes of the Mentionable to search for GFM references.
def attr_mentionable *attrs
def attr_mentionable(*attrs)
mentionable_attrs.concat(attrs.map(&:to_s))
end
......@@ -38,7 +38,7 @@ module Mentionable
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def has_mentioned? target
def has_mentioned?(target)
Note.cross_reference_exists?(target, local_reference)
end
......@@ -64,7 +64,7 @@ module Mentionable
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def references p = project, text = mentionable_text
def references(p = project, text = mentionable_text)
return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new
ext.analyze(text)
......@@ -73,7 +73,7 @@ module Mentionable
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 = []
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)
......@@ -82,7 +82,7 @@ module Mentionable
# 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
def notice_added_references(p = project, a = author)
ch = changed_attributes
original, mentionable_changed = "", false
self.class.mentionable_attrs.each do |attr|
......
# == Notifiable concern
#
# Contains notification functionality shared between UsersProject and UsersGroup
# Contains notification functionality
#
module Notifiable
extend ActiveSupport::Concern
......
......@@ -17,8 +17,8 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Group < Namespace
has_many :users_groups, dependent: :destroy
has_many :users, through: :users_groups
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :users, through: :group_members
has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project
has_many :ldap_group_links, foreign_key: 'group_id', dependent: :destroy
......@@ -33,22 +33,22 @@ class Group < Namespace
end
def owners
@owners ||= users_groups.owners.map(&:user)
@owners ||= group_members.owners.map(&:user)
end
def add_users(user_ids, group_access)
def add_users(user_ids, access_level)
user_ids.compact.each do |user_id|
user = self.users_groups.find_or_initialize_by(user_id: user_id)
user.update_attributes(group_access: group_access)
user = self.group_members.find_or_initialize_by(user_id: user_id)
user.update_attributes(access_level: access_level)
end
end
def add_user(user, group_access)
self.users_groups.create(user_id: user.id, group_access: group_access)
def add_user(user, access_level)
self.group_members.create(user_id: user.id, access_level: access_level)
end
def add_owner(user)
self.add_user(user, UsersGroup::OWNER)
self.add_user(user, Gitlab::Access::OWNER)
end
def has_owner?(user)
......@@ -64,7 +64,7 @@ class Group < Namespace
end
def members
users_groups
group_members
end
def avatar_type
......@@ -90,6 +90,10 @@ class Group < Namespace
ldap_group_links.first.try(:group_access)
end
def ldap_synced?
ldap_cn.present?
end
class << self
def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
......
......@@ -21,6 +21,7 @@ class WebHook < ActiveRecord::Base
default_value_for :push_events, true
default_value_for :issues_events, false
default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
......
......@@ -48,6 +48,10 @@ class Issue < ActiveRecord::Base
state :closed
end
def hook_attrs
attributes
end
# Mentionable overrides.
def gfm_reference
......@@ -65,4 +69,9 @@ class Issue < ActiveRecord::Base
def reset_events_cache
Event.reset_event_cache_for(self)
end
# To allow polymorphism with MergeRequest.
def source_project
project
end
end
......@@ -4,7 +4,7 @@ class LdapGroupLink < ActiveRecord::Base
validates :cn, :group_access, :group_id, presence: true
validates :cn, uniqueness: { scope: :group_id }
validates :group_access, inclusion: { in: UsersGroup.group_access_roles.values }
validates :group_access, inclusion: { in: Gitlab::Access.all_values }
def access_field
group_access
......
class Member < ActiveRecord::Base
include Notifiable
include Gitlab::Access
belongs_to :user
belongs_to :source, polymorphic: true
validates :user, presence: true
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
scope :developers, -> { where(access_level: DEVELOPER) }
scope :masters, -> { where(access_level: MASTER) }
scope :owners, -> { where(access_level: OWNER) }
delegate :name, :username, :email, to: :user, prefix: true
end
class GroupMember < Member
SOURCE_TYPE = 'Namespace'
belongs_to :group, class_name: 'Group', foreign_key: 'source_id'
# Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE
default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\ANamespace\z/
default_scope { where(source_type: SOURCE_TYPE) }
scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
scope :with_ldap_dn, -> { references(:user).includes(:user).
where(users: { provider: 'ldap' }) }
after_create :notify_create
after_update :notify_update
def self.access_level_roles
Gitlab::Access.options_with_owner
end
def group
source
end
def access_field
access_level
end
def notify_create
notification_service.new_group_member(self)
end
def notify_update
if access_level_changed?
notification_service.update_group_member(self)
end
end
def notification_service
NotificationService.new
end
end
# == Schema Information
#
# Table name: users_projects
#
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# project_access :integer default(0), not null
# notification_level :integer default(3), not null
#
class UsersProject < ActiveRecord::Base
include Gitlab::ShellAdapter
include Notifiable
include Gitlab::Access
belongs_to :user
belongs_to :project
class ProjectMember < Member
SOURCE_TYPE = 'Project'
validates :user, presence: true
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_access, inclusion: { in: Gitlab::Access.values }, presence: true
validates :project, presence: true
include Gitlab::ShellAdapter
delegate :name, :username, :email, to: :user, prefix: true
belongs_to :project, class_name: 'Project', foreign_key: 'source_id'
scope :guests, -> { where(project_access: GUEST) }
scope :reporters, -> { where(project_access: REPORTER) }
scope :developers, -> { where(project_access: DEVELOPER) }
scope :masters, -> { where(project_access: MASTER) }
scope :in_project, ->(project) { where(project_id: project.id) }
scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) }
scope :with_user, ->(user) { where(user_id: user.id) }
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\AProject\z/
default_scope { where(source_type: SOURCE_TYPE) }
after_create :post_create_hook
after_update :post_update_hook
after_destroy :post_destroy_hook
scope :in_project, ->(project) { where(source_id: project.id) }
scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) }
scope :with_user, ->(user) { where(user_id: user.id) }
class << self
# Add users to project teams with passed access option
......@@ -50,7 +31,7 @@ class UsersProject < ActiveRecord::Base
# add_users_into_projects(
# project_ids,
# user_ids,
# UsersProject::MASTER
# ProjectMember::MASTER
# )
#
# add_users_into_projects(
......@@ -60,20 +41,20 @@ class UsersProject < ActiveRecord::Base
# )
#
def add_users_into_projects(project_ids, user_ids, access)
project_access = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
access
else
raise "Non valid access"
end
UsersProject.transaction do
access_level = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
access
else
raise "Non valid access"
end
ProjectMember.transaction do
project_ids.each do |project_id|
user_ids.each do |user_id|
users_project = UsersProject.new(project_access: project_access, user_id: user_id)
users_project.project_id = project_id
users_project.save
member = ProjectMember.new(access_level: access_level, user_id: user_id)
member.source_id = project_id
member.save
end
end
end
......@@ -84,10 +65,10 @@ class UsersProject < ActiveRecord::Base
end
def truncate_teams(project_ids)
UsersProject.transaction do
users_projects = UsersProject.where(project_id: project_ids)
users_projects.each do |users_project|
users_project.destroy
ProjectMember.transaction do
members = ProjectMember.where(source_id: project_ids)
members.each do |member|
member.destroy
end
end
......@@ -96,7 +77,7 @@ class UsersProject < ActiveRecord::Base
false
end
def truncate_team project
def truncate_team(project)
truncate_teams [project.id]
end
......@@ -110,7 +91,7 @@ class UsersProject < ActiveRecord::Base
end
def access_field
project_access
access_level
end
def owner?
......@@ -129,7 +110,7 @@ class UsersProject < ActiveRecord::Base
end
def post_update_hook
notification_service.update_team_member(self) if self.project_access_changed?
notification_service.update_team_member(self) if self.access_level_changed?
end
def post_destroy_hook
......@@ -149,4 +130,8 @@ class UsersProject < ActiveRecord::Base
def system_hook_service
SystemHooksService.new
end
def project
source
end
end
......@@ -122,9 +122,11 @@ class MergeRequest < ActiveRecord::Base
if opened? || reopened?
similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
if similar_mrs.any?
errors.add :base, "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
errors.add :validate_branches,
"Cannot Create: This merge request already exists: #{
similar_mrs.pluck(:title)
}"
end
end
end
......@@ -140,7 +142,8 @@ class MergeRequest < ActiveRecord::Base
if source_project.forked_from?(target_project)
true
else
errors.add :base, "Source project is not a fork of target project"
errors.add :validate_fork,
'Source project is not a fork of target project'
end
end
end
......@@ -208,6 +211,20 @@ class MergeRequest < ActiveRecord::Base
Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
end
def hook_attrs
attrs = {
source: source_project.hook_attrs,
target: target_project.hook_attrs,
last_commit: nil
}
unless last_commit.nil?
attrs.merge!(last_commit: last_commit.hook_attrs(source_project))
end
attributes.merge!(attrs)
end
def for_fork?
target_project != source_project
end
......
......@@ -38,7 +38,7 @@ class Namespace < ActiveRecord::Base
scope :root, -> { where('type IS NULL') }
def self.search query
def self.search(query)
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
......
......@@ -6,7 +6,7 @@ module Network
@max_count ||= 650
end
def initialize project, ref, commit, filter_ref
def initialize(project, ref, commit, filter_ref)
@project = project
@ref = ref
@commit = commit
......
......@@ -83,8 +83,8 @@ class Project < ActiveRecord::Base
has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :protected_branches, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :users, through: :users_projects
has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember'
has_many :users, through: :project_members
has_many :deploy_keys_projects, dependent: :destroy
has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy
......@@ -129,6 +129,7 @@ class Project < ActiveRecord::Base
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :in_group_namespace, -> { joins(:group) }
scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
scope :sorted_by_stars, -> { reorder("projects.star_count DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
......@@ -345,7 +346,7 @@ class Project < ActiveRecord::Base
path
end
def items_for entity
def items_for(entity)
case entity
when 'issue' then
issues
......@@ -368,12 +369,12 @@ class Project < ActiveRecord::Base
def team_member_by_name_or_email(name = nil, email = nil)
user = users.where("name like ? or email like ?", name, email).first
users_projects.where(user: user) if user
project_members.where(user: user) if user
end
# Get Team Member record by user id
def team_member_by_id(user_id)
users_projects.find_by(user_id: user_id)
project_members.find_by(user_id: user_id)
end
def name_with_namespace
......@@ -437,15 +438,19 @@ class Project < ActiveRecord::Base
end
# Add comment about pushing new commits to merge requests
mrs = self.merge_requests.opened.where(source_branch: branch_name).to_a
comment_mr_with_commits(branch_name, commits, user)
true
end
def comment_mr_with_commits(branch_name, commits, user)
mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a
mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a
mrs.uniq.each do |merge_request|
Note.create_new_commits_note(merge_request, merge_request.project,
user, commits)
end
true
end
def valid_repo?
......@@ -514,7 +519,7 @@ class Project < ActiveRecord::Base
end
# 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)
end
......@@ -554,6 +559,16 @@ class Project < ActiveRecord::Base
end
end
def hook_attrs
{
name: name,
ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo,
namespace: namespace.name,
visibility_level: visibility_level
}
end
# Reset events cache related to this project
#
# Since we do cache @event we need to reset cache in special cases:
......@@ -570,7 +585,7 @@ class Project < ActiveRecord::Base
end
def project_member(user)
users_projects.where(user_id: user).first
project_members.where(user_id: user).first
end
def default_branch
......@@ -614,4 +629,16 @@ class Project < ActiveRecord::Base
def find_label(name)
labels.find_by(name: name)
end
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end
def group_ldap_synced?
if group
group.ldap_synced?
else
false
end
end
end
......@@ -27,12 +27,17 @@ class GitlabCiService < CiService
hook.save
end
def commit_status_path sha
def commit_status_path(sha)
project_url + "/builds/#{sha}/status.json?token=#{token}"
end
def commit_status sha
response = HTTParty.get(commit_status_path(sha), verify: false)
def get_ci_build(sha)
@ci_builds ||= {}
@ci_builds[sha] ||= HTTParty.get(commit_status_path(sha), verify: false)
end
def commit_status(sha)
response = get_ci_build(sha)
if response.code == 200 and response["status"]
response["status"]
......@@ -41,7 +46,15 @@ class GitlabCiService < CiService
end
end
def build_page sha
def commit_coverage(sha)
response = get_ci_build(sha)
if response.code == 200 and response["coverage"]
response["coverage"]
end
end
def build_page(sha)
project_url + "/builds/#{sha}"
end
......
......@@ -11,7 +11,7 @@ class ProjectTeam
# @team << [@user, :master]
# @team << [@users, :master]
#
def << args
def <<(args)
users = args.first
if users.respond_to?(:each)
......@@ -32,12 +32,12 @@ class ProjectTeam
end
def find_tm(user_id)
tm = project.users_projects.find_by(user_id: user_id)
tm = project.project_members.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)
tm = group.group_members.find_by(user_id: user_id)
end
tm
......@@ -52,7 +52,7 @@ class ProjectTeam
end
def add_users_ids(user_ids, access)
UsersProject.add_users_into_projects(
ProjectMember.add_users_into_projects(
[project.id],
user_ids,
access
......@@ -61,7 +61,7 @@ class ProjectTeam
# Remove all users from project team
def truncate
UsersProject.truncate_team(project)
ProjectMember.truncate_team(project)
end
def users
......@@ -91,8 +91,8 @@ class ProjectTeam
def import(source_project)
target_project = project
source_team = source_project.users_projects.to_a
target_user_ids = target_project.users_projects.pluck(:user_id)
source_team = source_project.project_members.to_a
target_user_ids = target_project.project_members.pluck(:user_id)
source_team.reject! do |tm|
# Skip if user already present in team
......@@ -102,11 +102,11 @@ class ProjectTeam
source_team.map! do |tm|
new_tm = tm.dup
new_tm.id = nil
new_tm.project_id = target_project.id
new_tm.source = target_project
new_tm
end
UsersProject.transaction do
ProjectMember.transaction do
source_team.each do |tm|
tm.save
end
......@@ -135,10 +135,10 @@ class ProjectTeam
def max_tm_access(user_id)
access = []
access << project.users_projects.find_by(user_id: user_id).try(:access_field)
access << project.project_members.find_by(user_id: user_id).try(:access_field)
if group
access << group.users_groups.find_by(user_id: user_id).try(:access_field)
access << group.group_members.find_by(user_id: user_id).try(:access_field)
end
if project.invited_groups.any?
......@@ -152,7 +152,7 @@ class ProjectTeam
def max_invited_level(user_id)
project.project_group_links.map do |group_link|
invited_group = group_link.group
access = invited_group.users_groups.find_by(user_id: user_id).try(:access_field)
access = invited_group.group_members.find_by(user_id: user_id).try(:access_field)
# If group member has higher access level we should restrict it
# to max allowed access level
......@@ -167,17 +167,17 @@ class ProjectTeam
private
def fetch_members(level = nil)
project_members = project.users_projects
group_members = group ? group.users_groups : []
project_members = project.project_members
group_members = group ? group.group_members : []
invited_members = []
if project.invited_groups.any?
project.project_group_links.each do |group_link|
invited_group = group_link.group
im = invited_group.users_groups
im = invited_group.group_members
if level
int_level = UsersGroup.group_access_roles[level.to_s.singularize.titleize]
int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize]
# Skip group members if we ask for masters
# but max group access is developers
......@@ -187,7 +187,7 @@ class ProjectTeam
# group access is developers we need to provide
# both group master, developers as devs
if int_level == group_link.group_access
im.where("group_access >= ?)", group_link.group_access)
im.where("access_level >= ?)", group_link.group_access)
else
im.send(level)
end
......
......@@ -107,6 +107,18 @@ class ProjectWiki
[title.gsub(/\.[^.]*$/, ""), title_array.join("/")]
end
def search_files(query)
repository.search_files(query, default_branch)
end
def repository
Repository.new(path_with_namespace, default_branch)
end
def default_branch
wiki.class.default_ref
end
private
def create_repo!
......
......@@ -25,14 +25,14 @@ class Repository
raw_repository.empty?
end
def commit(id = nil)
def commit(id = 'HEAD')
return nil unless raw_repository
commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit
commit
end
def commits(ref, path = nil, limit = nil, offset = nil)
def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
commits = Gitlab::Git::Commit.where(
repo: raw_repository,
ref: ref,
......@@ -137,8 +137,18 @@ class Repository
def graph_log
Rails.cache.fetch(cache_key(:graph_log)) do
stats = Gitlab::Git::GitStats.new(raw, root_ref, Gitlab.config.git.timeout)
stats.parsed_log
commits = raw_repository.log(limit: 6000, skip_merges: true,
ref: root_ref)
commits.map do |rugged_commit|
commit = Gitlab::Git::Commit.new(rugged_commit)
{
author_name: commit.author_name.force_encoding('UTF-8'),
author_email: commit.author_email.force_encoding('UTF-8'),
additions: commit.stats.additions,
deletions: commit.stats.deletions
}
end
end
end
......@@ -223,12 +233,15 @@ class Repository
end
def last_commit_for_path(sha, path)
commits(sha, path, 1).last
args = %W(git rev-list --max-count 1 #{sha} -- #{path})
sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
commit(sha)
end
# Remove archives older than 2 hours
def clean_old_archives
Gitlab::Popen.popen(%W(find #{Gitlab.config.gitlab.repository_downloads_path} -mmin +120 -delete))
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end
def branches_sorted_by(value)
......@@ -247,20 +260,18 @@ class Repository
end
def contributors
log = graph_log.group_by { |i| i[:author_email] }
commits = self.commits(nil, nil, 2000, 0, true)
log.map do |email, contributions|
commits.group_by(&:author_email).map do |email, commits|
contributor = Gitlab::Contributor.new
contributor.email = email
contributions.each do |contribution|
commits.each do |commit|
if contributor.name.blank?
contributor.name = contribution[:author_name]
contributor.name = commit.author_name
end
contributor.commits += 1
contributor.additions += contribution[:additions] || 0
contributor.deletions += contribution[:deletions] || 0
end
contributor
......@@ -282,4 +293,21 @@ class Repository
blob_at(commit.parent_id, diff.old_path)
end
end
def branch_names_contains(sha)
args = %W(git branch --contains #{sha})
names = Gitlab::Popen.popen(args, path_to_repo).first
if names.respond_to?(:split)
names = names.split("\n").map(&:strip)
names.each do |name|
name.slice! '* '
end
names
else
[]
end
end
end
......@@ -81,21 +81,23 @@ class User < ActiveRecord::Base
has_many :emails, dependent: :destroy
# Groups
has_many :users_groups, dependent: :destroy
has_many :groups, through: :users_groups
has_many :owned_groups, -> { where users_groups: { group_access: UsersGroup::OWNER } }, through: :users_groups, source: :group
has_many :masters_groups, -> { where users_groups: { group_access: UsersGroup::MASTER } }, through: :users_groups, source: :group
has_many :members, dependent: :destroy
has_many :project_members, source: 'ProjectMember'
has_many :group_members, source: 'GroupMember'
has_many :groups, through: :group_members
has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
# Projects
has_many :groups_projects, through: :groups, source: :projects
has_many :personal_projects, through: :namespace, source: :projects
has_many :projects, through: :users_projects
has_many :projects, through: :project_members
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :users_star_projects, dependent: :destroy
has_many :starred_projects, through: :users_star_projects, source: :project
has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
has_many :users_projects, dependent: :destroy
has_many :project_members, dependent: :destroy, class_name: 'ProjectMember'
has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
......@@ -140,7 +142,7 @@ class User < ActiveRecord::Base
state_machine :state, initial: :active do
after_transition any => :blocked do |user, transition|
# Remove user from all projects and
user.users_projects.find_each do |membership|
user.project_members.find_each do |membership|
# skip owned resources
next if membership.project.owner == user
......@@ -148,7 +150,7 @@ class User < ActiveRecord::Base
end
# Remove user from all groups
user.users_groups.find_each do |membership|
user.group_members.find_each do |membership|
# skip owned resources
next if membership.group.last_owner?(user)
......@@ -175,7 +177,7 @@ class User < ActiveRecord::Base
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) }
......@@ -202,7 +204,7 @@ class User < ActiveRecord::Base
User.where(name: name).first
end
def filter filter_name
def filter(filter_name)
case filter_name
when "admins"; self.admins
when "blocked"; self.blocked
......@@ -212,7 +214,7 @@ class User < ActiveRecord::Base
end
end
def search query
def search(query)
where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
end
......@@ -297,7 +299,7 @@ class User < ActiveRecord::Base
# Team membership in authorized projects
def tm_in_authorized_projects
UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id)
ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id)
end
def is_admin?
......@@ -332,7 +334,7 @@ class User < ActiveRecord::Base
several_namespaces? || admin
end
def can? action, subject
def can?(action, subject)
abilities.allowed?(self, action, subject)
end
......@@ -353,7 +355,7 @@ class User < ActiveRecord::Base
(personal_projects.count.to_f / projects_limit) * 100
end
def recent_push project_id = nil
def recent_push(project_id = nil)
# Get push events not earlier than 2 hours ago
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_id) if project_id
......@@ -382,11 +384,11 @@ class User < ActiveRecord::Base
project.team_member_by_id(self.id)
end
def already_forked? project
def already_forked?(project)
!!fork_of(project)
end
def fork_of project
def fork_of(project)
links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects)
if links.any?
......@@ -512,7 +514,7 @@ class User < ActiveRecord::Base
NotificationService.new
end
def log_info message
def log_info(message)
Gitlab::AppLogger.info message
end
......
# == Schema Information
#
# 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
# updated_at :datetime
# notification_level :integer default(3), not null
#
class UsersGroup < ActiveRecord::Base
include Notifiable
include Gitlab::Access
def self.group_access_roles
Gitlab::Access.options_with_owner
end
belongs_to :user
belongs_to :group
scope :guests, -> { where(group_access: GUEST) }
scope :reporters, -> { where(group_access: REPORTER) }
scope :developers, -> { where(group_access: DEVELOPER) }
scope :masters, -> { where(group_access: MASTER) }
scope :owners, -> { where(group_access: OWNER) }
scope :with_ldap_dn, -> { references(:user).includes(:user).
where(users: { provider: 'ldap' }) }
scope :with_group, ->(group) { where(group_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
after_create :notify_create
after_update :notify_update
validates :group_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true
validates :user_id, presence: true
validates :group_id, presence: true
validates :user_id, uniqueness: { scope: [:group_id], message: "already exists in group" }
delegate :name, :username, :email, to: :user, prefix: true
def access_field
group_access
end
def notify_create
notification_service.new_group_member(self)
end
def notify_update
if group_access_changed?
notification_service.update_group_member(self)
end
end
def notification_service
NotificationService.new
end
end
......@@ -87,14 +87,14 @@ class WikiPage
def version
return nil unless persisted?
@version ||= Commit.new(Gitlab::Git::Commit.new(@page.version))
@version ||= @page.version
end
# Returns an array of Gitlab Commit instances.
def versions
return [] unless persisted?
@page.versions.map { |v| Commit.new(Gitlab::Git::Commit.new(v)) }
@page.versions
end
def commit
......
class BaseService
attr_accessor :project, :current_user, :params
def initialize(project, user, params)
def initialize(project, user, params = {})
@project, @current_user, @params = project, user, params.dup
end
......@@ -25,11 +25,26 @@ class BaseService
EventCreateService.new
end
def log_info message
def log_info(message)
Gitlab::AppLogger.info message
end
def system_hook_service
SystemHooksService.new
end
private
def error(message)
{
message: message,
status: :error
}
end
def success
{
status: :success
}
end
end
class CreateBranchService
def execute(project, branch_name, ref, current_user)
require_relative 'base_service'
class CreateBranchService < BaseService
def execute(branch_name, ref)
valid_branch = Gitlab::GitRefValidator.validate(branch_name)
if valid_branch == false
return error('Branch name invalid')
......@@ -22,17 +24,9 @@ class CreateBranchService
end
end
def error(message)
{
message: message,
status: :error
}
end
def success(branch)
{
branch: branch,
status: :success
}
out = super()
out[:branch] = branch
out
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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